bpo-35712: Make using NotImplemented in a boolean context issue a deprecation warning (GH-13195)
This commit is contained in:
parent
ae75a29435
commit
469325c30e
|
@ -31,7 +31,7 @@ A small number of constants live in the built-in namespace. They are:
|
||||||
etc.) to indicate that the operation is not implemented with respect to
|
etc.) to indicate that the operation is not implemented with respect to
|
||||||
the other type; may be returned by the in-place binary special methods
|
the other type; may be returned by the in-place binary special methods
|
||||||
(e.g. :meth:`__imul__`, :meth:`__iand__`, etc.) for the same purpose.
|
(e.g. :meth:`__imul__`, :meth:`__iand__`, etc.) for the same purpose.
|
||||||
Its truth value is true.
|
It should not be evaluated in a boolean context.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -50,6 +50,11 @@ A small number of constants live in the built-in namespace. They are:
|
||||||
even though they have similar names and purposes.
|
even though they have similar names and purposes.
|
||||||
See :exc:`NotImplementedError` for details on when to use it.
|
See :exc:`NotImplementedError` for details on when to use it.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
Evaluating ``NotImplemented`` in a boolean context is deprecated. While
|
||||||
|
it currently evaluates as true, it will emit a :exc:`DeprecationWarning`.
|
||||||
|
It will raise a :exc:`TypeError` in a future version of Python.
|
||||||
|
|
||||||
|
|
||||||
.. index:: single: ...; ellipsis literal
|
.. index:: single: ...; ellipsis literal
|
||||||
.. data:: Ellipsis
|
.. data:: Ellipsis
|
||||||
|
|
|
@ -156,13 +156,18 @@ NotImplemented
|
||||||
object is accessed through the built-in name ``NotImplemented``. Numeric methods
|
object is accessed through the built-in name ``NotImplemented``. Numeric methods
|
||||||
and rich comparison methods should return this value if they do not implement the
|
and rich comparison methods should return this value if they do not implement the
|
||||||
operation for the operands provided. (The interpreter will then try the
|
operation for the operands provided. (The interpreter will then try the
|
||||||
reflected operation, or some other fallback, depending on the operator.) Its
|
reflected operation, or some other fallback, depending on the operator.) It
|
||||||
truth value is true.
|
should not be evaluated in a boolean context.
|
||||||
|
|
||||||
See
|
See
|
||||||
:ref:`implementing-the-arithmetic-operations`
|
:ref:`implementing-the-arithmetic-operations`
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
Evaluating ``NotImplemented`` in a boolean context is deprecated. While
|
||||||
|
it currently evaluates as true, it will emit a :exc:`DeprecationWarning`.
|
||||||
|
It will raise a :exc:`TypeError` in a future version of Python.
|
||||||
|
|
||||||
|
|
||||||
Ellipsis
|
Ellipsis
|
||||||
.. index::
|
.. index::
|
||||||
|
|
|
@ -452,6 +452,12 @@ Deprecated
|
||||||
of Python. For the majority of use cases, users can leverage the Abstract Syntax
|
of Python. For the majority of use cases, users can leverage the Abstract Syntax
|
||||||
Tree (AST) generation and compilation stage, using the :mod:`ast` module.
|
Tree (AST) generation and compilation stage, using the :mod:`ast` module.
|
||||||
|
|
||||||
|
* Using :data:`NotImplemented` in a boolean context has been deprecated,
|
||||||
|
as it is almost exclusively the result of incorrect rich comparator
|
||||||
|
implementations. It will be made a :exc:`TypeError` in a future version
|
||||||
|
of Python.
|
||||||
|
(Contributed by Josh Rosenberg in :issue:`35712`.)
|
||||||
|
|
||||||
* The :mod:`random` module currently accepts any hashable type as a
|
* The :mod:`random` module currently accepts any hashable type as a
|
||||||
possible seed value. Unfortunately, some of those types are not
|
possible seed value. Unfortunately, some of those types are not
|
||||||
guaranteed to have a deterministic hash value. After Python 3.9,
|
guaranteed to have a deterministic hash value. After Python 3.9,
|
||||||
|
|
|
@ -96,6 +96,8 @@ def _gt_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
def _le_from_lt(self, other, NotImplemented=NotImplemented):
|
def _le_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
||||||
op_result = self.__lt__(other)
|
op_result = self.__lt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
return op_result or self == other
|
return op_result or self == other
|
||||||
|
|
||||||
def _ge_from_lt(self, other, NotImplemented=NotImplemented):
|
def _ge_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
|
@ -136,6 +138,8 @@ def _lt_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
def _ge_from_gt(self, other, NotImplemented=NotImplemented):
|
def _ge_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
|
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
|
||||||
op_result = self.__gt__(other)
|
op_result = self.__gt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
return op_result or self == other
|
return op_result or self == other
|
||||||
|
|
||||||
def _le_from_gt(self, other, NotImplemented=NotImplemented):
|
def _le_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
|
|
|
@ -1398,7 +1398,7 @@ class IPv4Interface(IPv4Address):
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
address_equal = IPv4Address.__eq__(self, other)
|
address_equal = IPv4Address.__eq__(self, other)
|
||||||
if not address_equal or address_equal is NotImplemented:
|
if address_equal is NotImplemented or not address_equal:
|
||||||
return address_equal
|
return address_equal
|
||||||
try:
|
try:
|
||||||
return self.network == other.network
|
return self.network == other.network
|
||||||
|
@ -2096,7 +2096,7 @@ class IPv6Interface(IPv6Address):
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
address_equal = IPv6Address.__eq__(self, other)
|
address_equal = IPv6Address.__eq__(self, other)
|
||||||
if not address_equal or address_equal is NotImplemented:
|
if address_equal is NotImplemented or not address_equal:
|
||||||
return address_equal
|
return address_equal
|
||||||
try:
|
try:
|
||||||
return self.network == other.network
|
return self.network == other.network
|
||||||
|
@ -2109,7 +2109,7 @@ class IPv6Interface(IPv6Address):
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
address_less = IPv6Address.__lt__(self, other)
|
address_less = IPv6Address.__lt__(self, other)
|
||||||
if address_less is NotImplemented:
|
if address_less is NotImplemented:
|
||||||
return NotImplemented
|
return address_less
|
||||||
try:
|
try:
|
||||||
return (self.network < other.network or
|
return (self.network < other.network or
|
||||||
self.network == other.network and address_less)
|
self.network == other.network and address_less)
|
||||||
|
|
|
@ -2528,7 +2528,7 @@ class TestBufferProtocol(unittest.TestCase):
|
||||||
values = [INT(9), IDX(9),
|
values = [INT(9), IDX(9),
|
||||||
2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2),
|
2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2),
|
||||||
[1,2,3], {4,5,6}, {7:8}, (), (9,),
|
[1,2,3], {4,5,6}, {7:8}, (), (9,),
|
||||||
True, False, None, NotImplemented,
|
True, False, None, Ellipsis,
|
||||||
b'a', b'abc', bytearray(b'a'), bytearray(b'abc'),
|
b'a', b'abc', bytearray(b'a'), bytearray(b'abc'),
|
||||||
'a', 'abc', r'a', r'abc',
|
'a', 'abc', r'a', r'abc',
|
||||||
f, lambda x: x]
|
f, lambda x: x]
|
||||||
|
|
|
@ -1666,6 +1666,20 @@ class BuiltinTest(unittest.TestCase):
|
||||||
self.assertRaises(TypeError, tp, 1, 2)
|
self.assertRaises(TypeError, tp, 1, 2)
|
||||||
self.assertRaises(TypeError, tp, a=1, b=2)
|
self.assertRaises(TypeError, tp, a=1, b=2)
|
||||||
|
|
||||||
|
def test_warning_notimplemented(self):
|
||||||
|
# Issue #35712: NotImplemented is a sentinel value that should never
|
||||||
|
# be evaluated in a boolean context (virtually all such use cases
|
||||||
|
# are a result of accidental misuse implementing rich comparison
|
||||||
|
# operations in terms of one another).
|
||||||
|
# For the time being, it will continue to evaluate as truthy, but
|
||||||
|
# issue a deprecation warning (with the eventual intent to make it
|
||||||
|
# a TypeError).
|
||||||
|
self.assertWarns(DeprecationWarning, bool, NotImplemented)
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
self.assertTrue(NotImplemented)
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
self.assertFalse(not NotImplemented)
|
||||||
|
|
||||||
|
|
||||||
class TestBreakpoint(unittest.TestCase):
|
class TestBreakpoint(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -2526,9 +2526,9 @@ order (MRO) for bases """
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Two essentially featureless objects, just inheriting stuff from
|
# Two essentially featureless objects, (Ellipsis just inherits stuff
|
||||||
# object.
|
# from object.
|
||||||
self.assertEqual(dir(NotImplemented), dir(Ellipsis))
|
self.assertEqual(dir(object()), dir(Ellipsis))
|
||||||
|
|
||||||
# Nasty test case for proxied objects
|
# Nasty test case for proxied objects
|
||||||
class Wrapper(object):
|
class Wrapper(object):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Using :data:`NotImplemented` in a boolean context has been deprecated. Patch
|
||||||
|
contributed by Josh Rosenberg.
|
|
@ -1673,6 +1673,22 @@ notimplemented_dealloc(PyObject* ignore)
|
||||||
Py_FatalError("deallocating NotImplemented");
|
Py_FatalError("deallocating NotImplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
notimplemented_bool(PyObject *v)
|
||||||
|
{
|
||||||
|
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||||
|
"NotImplemented should not be used in a boolean context",
|
||||||
|
1) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyNumberMethods notimplemented_as_number = {
|
||||||
|
.nb_bool = notimplemented_bool,
|
||||||
|
};
|
||||||
|
|
||||||
PyTypeObject _PyNotImplemented_Type = {
|
PyTypeObject _PyNotImplemented_Type = {
|
||||||
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||||
"NotImplementedType",
|
"NotImplementedType",
|
||||||
|
@ -1683,8 +1699,8 @@ PyTypeObject _PyNotImplemented_Type = {
|
||||||
0, /*tp_getattr*/
|
0, /*tp_getattr*/
|
||||||
0, /*tp_setattr*/
|
0, /*tp_setattr*/
|
||||||
0, /*tp_as_async*/
|
0, /*tp_as_async*/
|
||||||
NotImplemented_repr, /*tp_repr*/
|
NotImplemented_repr, /*tp_repr*/
|
||||||
0, /*tp_as_number*/
|
¬implemented_as_number, /*tp_as_number*/
|
||||||
0, /*tp_as_sequence*/
|
0, /*tp_as_sequence*/
|
||||||
0, /*tp_as_mapping*/
|
0, /*tp_as_mapping*/
|
||||||
0, /*tp_hash */
|
0, /*tp_hash */
|
||||||
|
|
Loading…
Reference in New Issue