gh-82012: Deprecate bitwise inversion (~) of bool (#103487)

The bitwise inversion operator on bool returns the bitwise inversion of the
underlying int value; i.e. `~True == -2` such that `bool(~True) == True`.

It's a common pitfall that users mistake `~` as negation operator and actually
want `not`. Supporting `~` is an artifact of bool inheriting from int. Since there
is no real use-case for the current behavior, let's deprecate `~` on bool and
later raise an error. This removes a potential source errors for users.

Full reasoning: https://github.com/python/cpython/issues/82012#issuecomment-1258705971

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
This commit is contained in:
Tim Hoffmann 2023-05-03 09:00:42 +02:00 committed by GitHub
parent 5b05b013ff
commit fdb3ef8c0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 25 deletions

View file

@ -147,7 +147,7 @@ are always available. They are listed here in alphabetical order.
or omitted, this returns ``False``; otherwise, it returns ``True``. The
:class:`bool` class is a subclass of :class:`int` (see :ref:`typesnumeric`).
It cannot be subclassed further. Its only instances are ``False`` and
``True`` (see :ref:`bltin-boolean-values`).
``True`` (see :ref:`typebool`).
.. index:: pair: Boolean; type

View file

@ -802,6 +802,39 @@ number, :class:`float`, or :class:`complex`::
hash_value = -2
return hash_value
.. _typebool:
Boolean Type - :class:`bool`
============================
Booleans represent truth values. The :class:`bool` type has exactly two
constant instances: ``True`` and ``False``.
.. index::
single: False
single: True
pair: Boolean; values
The built-in function :func:`bool` converts any value to a boolean, if the
value can be interpreted as a truth value (see section :ref:`truth` above).
For logical operations, use the :ref:`boolean operators <boolean>` ``and``,
``or`` and ``not``.
When applying the bitwise operators ``&``, ``|``, ``^`` to two booleans, they
return a bool equivalent to the logical operations "and", "or", "xor". However,
the logical operators ``and``, ``or`` and ``!=`` should be preferred
over ``&``, ``|`` and ``^``.
.. deprecated:: 3.12
The use of the bitwise inversion operator ``~`` is deprecated and will
raise an error in Python 3.14.
:class:`bool` is a subclass of :class:`int` (see :ref:`typesnumeric`). In
many numeric contexts, ``False`` and ``True`` behave like the integers 0 and 1, respectively.
However, relying on this is discouraged; explicitly convert using :func:`int`
instead.
.. _typeiter:
Iterator Types
@ -5394,27 +5427,6 @@ information. There is exactly one ``NotImplemented`` object.
It is written as ``NotImplemented``.
.. _bltin-boolean-values:
Boolean Values
--------------
Boolean values are the two constant objects ``False`` and ``True``. They are
used to represent truth values (although other values can also be considered
false or true). In numeric contexts (for example when used as the argument to
an arithmetic operator), they behave like the integers 0 and 1, respectively.
The built-in function :func:`bool` can be used to convert any value to a
Boolean, if the value can be interpreted as a truth value (see section
:ref:`truth` above).
.. index::
single: False
single: True
pair: Boolean; values
They are written as ``False`` and ``True``, respectively.
.. _typesinternal:
Internal Objects

View file

@ -710,6 +710,12 @@ Deprecated
replaced by :data:`calendar.Month.JANUARY` and :data:`calendar.Month.FEBRUARY`.
(Contributed by Prince Roshan in :gh:`103636`.)
* The bitwise inversion operator (``~``) on bool is deprecated. It will throw an
error in Python 3.14. Use ``not`` for logical negation of bools instead.
In the rare case that you really need the bitwise inversion of the underlying
``int``, convert to int explicitly with ``~int(x)``. (Contributed by Tim Hoffmann
in :gh:`103487`.)
Pending Removal in Python 3.13
------------------------------

View file

@ -58,8 +58,22 @@ def test_math(self):
self.assertEqual(-True, -1)
self.assertEqual(abs(True), 1)
self.assertIsNot(abs(True), True)
self.assertEqual(~False, -1)
self.assertEqual(~True, -2)
with self.assertWarns(DeprecationWarning):
# We need to put the bool in a variable, because the constant
# ~False is evaluated at compile time due to constant folding;
# consequently the DeprecationWarning would be issued during
# module loading and not during test execution.
false = False
self.assertEqual(~false, -1)
with self.assertWarns(DeprecationWarning):
# also check that the warning is issued in case of constant
# folding at compile time
self.assertEqual(eval("~False"), -1)
with self.assertWarns(DeprecationWarning):
true = True
self.assertEqual(~true, -2)
with self.assertWarns(DeprecationWarning):
self.assertEqual(eval("~True"), -2)
self.assertEqual(False+2, 2)
self.assertEqual(True+2, 3)

View file

@ -0,0 +1,5 @@
The bitwise inversion operator (``~``) on bool is deprecated.
It returns the bitwise inversion of the underlying ``int`` representation such that
``bool(~True) == True``, which can be confusing. Use ``not`` for logical negation
of bools. In the rare case that you really need the bitwise inversion of the underlying ``int``,
convert to int explicitly ``~int(x)``.

View file

@ -73,6 +73,22 @@ bool_vectorcall(PyObject *type, PyObject * const*args,
/* Arithmetic operations redefined to return bool if both args are bool. */
static PyObject *
bool_invert(PyObject *v)
{
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Bitwise inversion '~' on bool is deprecated. This "
"returns the bitwise inversion of the underlying int "
"object and is usually not what you expect from negating "
"a bool. Use the 'not' operator for boolean negation or "
"~int(x) if you really want the bitwise inversion of the "
"underlying int.",
1) < 0) {
return NULL;
}
return PyLong_Type.tp_as_number->nb_invert(v);
}
static PyObject *
bool_and(PyObject *a, PyObject *b)
{
@ -119,7 +135,7 @@ static PyNumberMethods bool_as_number = {
0, /* nb_positive */
0, /* nb_absolute */
0, /* nb_bool */
0, /* nb_invert */
(unaryfunc)bool_invert, /* nb_invert */
0, /* nb_lshift */
0, /* nb_rshift */
bool_and, /* nb_and */