From 1f0351ab6ef8e734f990620bd224c2b857b8e623 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 12 Apr 2023 21:49:30 +0200 Subject: [PATCH 1/5] gh-82012: Deprecate bitwise inversion (~) of bool 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 --- Doc/whatsnew/3.12.rst | 6 ++++++ Lib/test/test_bool.py | 18 ++++++++++++++++-- ...23-04-12-19-55-24.gh-issue-82012.FlcJAh.rst | 5 +++++ Objects/boolobject.c | 18 +++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-04-12-19-55-24.gh-issue-82012.FlcJAh.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index f4ee30b0d4d9eb..a342140b03b74f 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -705,6 +705,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 916e22a527a8e0..34ecb45f161dfe 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 00000000000000..819a2359bf6fae --- /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 597a76fa5cb162..0300f7bb4e3dc0 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 */ From 3632ba5d40540462c1e5e287d7e36796f85f65f9 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 30 Apr 2023 23:39:02 +0200 Subject: [PATCH 2/5] Document bool behavior for bitwise operations This also pulls the bool type to top-level of the type description page. Before it was only documented in the section "Other Built-in Types / Boolean Values". --- Doc/library/stdtypes.rst | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 2360472b31f175..b66ba739113268 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -802,6 +802,31 @@ 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``. + +Other types can be cast to bool explicitly using the function :func:`bool`. +Additionally, they are implicitly interpreted as bool in some contexts - see +:ref:`truth`. + +For logical operations, use the :ref:`boolean operators ` ``and``, +``or`` and ``not``. +When applying the bitwise operators ``&``, ``|``, ``^`` to two booleans, they +return a bool equivalent to the logical operations "and", "or", "xor". Still, +for ``&`` and ``|``, the logical operators ``and`` and ``or`` are preferred. +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, bools behave like the integers 0 and 1, respectively. +However, relying on this is discouraged; explicitly convert using :func:`int` +instead. + .. _typeiter: Iterator Types @@ -5399,21 +5424,14 @@ It is written as ``NotImplemented``. 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). +Boolean values are the two constant objects ``False`` and ``True``. See +:ref:`typebool` for more info. .. index:: single: False single: True pair: Boolean; values -They are written as ``False`` and ``True``, respectively. - .. _typesinternal: From 2cbfe2da9c7a7c778ca18e01b0ac9225c119eca7 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 1 May 2023 02:43:57 +0200 Subject: [PATCH 3/5] Move section "Boolean Values" into "Boolean Type" --- Doc/library/functions.rst | 2 +- Doc/library/stdtypes.rst | 33 +++++++++++++-------------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index a5e86ef0f9eb59..085a11c3caa708 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 b66ba739113268..5c70ba27f90ad4 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -808,19 +808,26 @@ Boolean Type - :class:`bool` ============================ Booleans represent truth values. The :class:`bool` type has exactly two -constant instances ``True`` and ``False``. +constant instances: ``True`` and ``False``. -Other types can be cast to bool explicitly using the function :func:`bool`. -Additionally, they are implicitly interpreted as bool in some contexts - see -:ref:`truth`. +.. 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 ` ``and``, ``or`` and ``not``. When applying the bitwise operators ``&``, ``|``, ``^`` to two booleans, they return a bool equivalent to the logical operations "and", "or", "xor". Still, for ``&`` and ``|``, the logical operators ``and`` and ``or`` are preferred. -The use of the bitwise inversion operator ``~`` is deprecated and will raise -an error in Python 3.14. + +.. 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, bools behave like the integers 0 and 1, respectively. @@ -5419,20 +5426,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``. See -:ref:`typebool` for more info. - -.. index:: - single: False - single: True - pair: Boolean; values - - .. _typesinternal: Internal Objects From e5ec711a01b427410b5010058d59ebd7a3ad7dfe Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 1 May 2023 08:21:27 +0200 Subject: [PATCH 4/5] Update Doc/library/stdtypes.rst Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- Doc/library/stdtypes.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 5c70ba27f90ad4..4b4d56aadc1636 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -821,8 +821,9 @@ value can be interpreted as a truth value (see section :ref:`truth` above). For logical operations, use the :ref:`boolean operators ` ``and``, ``or`` and ``not``. When applying the bitwise operators ``&``, ``|``, ``^`` to two booleans, they -return a bool equivalent to the logical operations "and", "or", "xor". Still, -for ``&`` and ``|``, the logical operators ``and`` and ``or`` are preferred. +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 From 2db5864da0f0207d0bb475222493aeed4bded8be Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 1 May 2023 08:21:48 +0200 Subject: [PATCH 5/5] Update Doc/library/stdtypes.rst Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- Doc/library/stdtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 4b4d56aadc1636..f6662b4336c276 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -831,7 +831,7 @@ over ``&``, ``|`` and ``^``. raise an error in Python 3.14. :class:`bool` is a subclass of :class:`int` (see :ref:`typesnumeric`). In -many numeric contexts, bools behave like the integers 0 and 1, respectively. +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.