From d01f24365689dfec8bbe5c9e37ff1274c55fb53f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 8 May 2026 07:44:04 +0300 Subject: [PATCH] gh-149532: Remove `bool.__invert__` magic method --- Doc/library/stdtypes.rst | 5 --- Doc/whatsnew/3.16.rst | 10 +++++ Lib/test/test_bool.py | 37 +++++++++++-------- ...-05-08-07-43-01.gh-issue-149532.SSltRW.rst | 2 + Objects/boolobject.c | 14 ++----- 5 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-08-07-43-01.gh-issue-149532.SSltRW.rst diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 3d943566be34ff..1ed6030c9e781a 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -881,11 +881,6 @@ 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.16. - :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` diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index cc88a694608233..053e3020f09d20 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -106,6 +106,16 @@ module_name Removed ======= +builtins +-------- + +* Bitwise inversion on boolean types, ``~True`` or ``~False`` + has been deprecated since Python 3.12, + as it produces surprising and unintuitive results (``-2`` and ``-1``). + Use ``not x`` instead for the logical negation of a Boolean. + In the rare case that you need the bitwise inversion of + the underlying integer, convert to ``int`` explicitly (``~int(x)``). + sysconfig --------- diff --git a/Lib/test/test_bool.py b/Lib/test/test_bool.py index dcdf7bdce03b80..506c084e509125 100644 --- a/Lib/test/test_bool.py +++ b/Lib/test/test_bool.py @@ -4,6 +4,8 @@ from test.support import os_helper import os +import re + class BoolTest(unittest.TestCase): @@ -58,22 +60,6 @@ def test_math(self): self.assertEqual(-True, -1) self.assertEqual(abs(True), 1) self.assertIsNot(abs(True), True) - 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) @@ -169,6 +155,25 @@ def test_math(self): self.assertIs(not True, False) self.assertIs(not False, True) + def test_invert(self): + # See gh-149532 + msg = re.escape("bad operand type for unary ~: 'bool'") + + # Check constants in case of a folding: + with self.assertRaisesRegex(TypeError, msg): + ~False + with self.assertRaisesRegex(TypeError, msg): + ~True + + # Check variable: + for bool_val in [True, False]: + with self.subTest(bool_val), self.assertRaisesRegex(TypeError, msg): + ~bool_val + + # Check `eval`: + with self.assertRaisesRegex(TypeError, msg): + eval("~False") + def test_convert(self): self.assertRaises(TypeError, bool, 42, 42) self.assertIs(bool(10), True) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-08-07-43-01.gh-issue-149532.SSltRW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-08-07-43-01.gh-issue-149532.SSltRW.rst new file mode 100644 index 00000000000000..b0d9df51f63cf2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-08-07-43-01.gh-issue-149532.SSltRW.rst @@ -0,0 +1,2 @@ +Remove :meth:`!bool.__invert__` magic method, which was deprecated since +3.12. diff --git a/Objects/boolobject.c b/Objects/boolobject.c index b694691ae4d0d1..413f8fdfb7cdfa 100644 --- a/Objects/boolobject.c +++ b/Objects/boolobject.c @@ -70,17 +70,9 @@ bool_vectorcall(PyObject *type, PyObject * const*args, static PyObject * bool_invert(PyObject *v) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Bitwise inversion '~' on bool is deprecated and will be removed in " - "Python 3.16. 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); + // This method is needed to shadow the `int` base method: + PyErr_SetString(PyExc_TypeError, "bad operand type for unary ~: 'bool'"); + return NULL; } static PyObject *