[Python-checkins] gh-82012: Deprecate bitwise inversion (~) of bool (#103487)

hauntsaninja webhook-mailer at python.org
Wed May 3 03:00:56 EDT 2023


https://github.com/python/cpython/commit/fdb3ef8c0f94c7e55870a585dc6499aca46f9f90
commit: fdb3ef8c0f94c7e55870a585dc6499aca46f9f90
branch: main
author: Tim Hoffmann <2836374+timhoffm at users.noreply.github.com>
committer: hauntsaninja <12621235+hauntsaninja at users.noreply.github.com>
date: 2023-05-03T00:00:42-07:00
summary:

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 at gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2023-04-12-19-55-24.gh-issue-82012.FlcJAh.rst
M Doc/library/functions.rst
M Doc/library/stdtypes.rst
M Doc/whatsnew/3.12.rst
M Lib/test/test_bool.py
M Objects/boolobject.c

diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index a5e86ef0f9eb..085a11c3caa7 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -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
 
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index 2360472b31f1..f6662b4336c2 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -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
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index a3fce7ccacf7..5f8a1f08026d 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -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
 ------------------------------
 
diff --git a/Lib/test/test_bool.py b/Lib/test/test_bool.py
index 916e22a527a8..34ecb45f161d 100644
--- a/Lib/test/test_bool.py
+++ b/Lib/test/test_bool.py
@@ -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)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-04-12-19-55-24.gh-issue-82012.FlcJAh.rst b/Misc/NEWS.d/next/Core and Builtins/2023-04-12-19-55-24.gh-issue-82012.FlcJAh.rst
new file mode 100644
index 000000000000..819a2359bf6f
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-04-12-19-55-24.gh-issue-82012.FlcJAh.rst	
@@ -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)``.
diff --git a/Objects/boolobject.c b/Objects/boolobject.c
index 597a76fa5cb1..0300f7bb4e3d 100644
--- a/Objects/boolobject.c
+++ b/Objects/boolobject.c
@@ -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 */



More information about the Python-checkins mailing list