bpo-35712: Make using NotImplemented in a boolean context issue a deprecation warning (GH-13195)

This commit is contained in:
MojoVampire 2020-03-03 18:50:17 +00:00 committed by GitHub
parent ae75a29435
commit 469325c30e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 64 additions and 12 deletions

View file

@ -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
the other type; may be returned by the in-place binary special methods
(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::
@ -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.
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
.. data:: Ellipsis

View file

@ -156,13 +156,18 @@ NotImplemented
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
operation for the operands provided. (The interpreter will then try the
reflected operation, or some other fallback, depending on the operator.) Its
truth value is true.
reflected operation, or some other fallback, depending on the operator.) It
should not be evaluated in a boolean context.
See
:ref:`implementing-the-arithmetic-operations`
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
.. index::

View file

@ -452,6 +452,12 @@ Deprecated
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.
* 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
possible seed value. Unfortunately, some of those types are not
guaranteed to have a deterministic hash value. After Python 3.9,

View file

@ -96,6 +96,8 @@ def _gt_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).'
op_result = self.__lt__(other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
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):
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
op_result = self.__gt__(other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
def _le_from_gt(self, other, NotImplemented=NotImplemented):

View file

@ -1398,7 +1398,7 @@ def __str__(self):
def __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
try:
return self.network == other.network
@ -2096,7 +2096,7 @@ def __str__(self):
def __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
try:
return self.network == other.network
@ -2109,7 +2109,7 @@ def __eq__(self, other):
def __lt__(self, other):
address_less = IPv6Address.__lt__(self, other)
if address_less is NotImplemented:
return NotImplemented
return address_less
try:
return (self.network < other.network or
self.network == other.network and address_less)

View file

@ -2528,7 +2528,7 @@ def f(): return 7
values = [INT(9), IDX(9),
2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2),
[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'),
'a', 'abc', r'a', r'abc',
f, lambda x: x]

View file

@ -1666,6 +1666,20 @@ def test_construct_singletons(self):
self.assertRaises(TypeError, tp, 1, 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):
def setUp(self):

View file

@ -2526,9 +2526,9 @@ def getdict(self):
except TypeError:
pass
# Two essentially featureless objects, just inheriting stuff from
# object.
self.assertEqual(dir(NotImplemented), dir(Ellipsis))
# Two essentially featureless objects, (Ellipsis just inherits stuff
# from object.
self.assertEqual(dir(object()), dir(Ellipsis))
# Nasty test case for proxied objects
class Wrapper(object):

View file

@ -0,0 +1,2 @@
Using :data:`NotImplemented` in a boolean context has been deprecated. Patch
contributed by Josh Rosenberg.

View file

@ -1673,6 +1673,22 @@ notimplemented_dealloc(PyObject* ignore)
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 = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"NotImplementedType",
@ -1683,8 +1699,8 @@ PyTypeObject _PyNotImplemented_Type = {
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
NotImplemented_repr, /*tp_repr*/
0, /*tp_as_number*/
NotImplemented_repr, /*tp_repr*/
&notimplemented_as_number, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */