bpo-44977: Deprecate delegation of int to __trunc__ (GH-31031)

Calling int(a) when type(a) implements __trunc__ but not __int__
or __index__ now raises a DeprecationWarning.
This commit is contained in:
Zackery Spytz 2022-02-03 01:43:25 -08:00 committed by GitHub
parent 7ffe7ba30f
commit b4bd1e1422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 40 additions and 11 deletions

View File

@ -891,6 +891,9 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.8 .. versionchanged:: 3.8
Falls back to :meth:`__index__` if :meth:`__int__` is not defined. Falls back to :meth:`__index__` if :meth:`__int__` is not defined.
.. versionchanged:: 3.11
The delegation to :meth:`__trunc__` is deprecated.
.. function:: isinstance(object, classinfo) .. function:: isinstance(object, classinfo)

View File

@ -2760,6 +2760,9 @@ left undefined.
The built-in function :func:`int` falls back to :meth:`__trunc__` if neither The built-in function :func:`int` falls back to :meth:`__trunc__` if neither
:meth:`__int__` nor :meth:`__index__` is defined. :meth:`__int__` nor :meth:`__index__` is defined.
.. versionchanged:: 3.11
The delegation of :func:`int` to :meth:`__trunc__` is deprecated.
.. _context-managers: .. _context-managers:

View File

@ -458,6 +458,11 @@ Deprecated
as deprecated, its docstring is now corrected). as deprecated, its docstring is now corrected).
(Contributed by Hugo van Kemenade in :issue:`45837`.) (Contributed by Hugo van Kemenade in :issue:`45837`.)
* The delegation of :func:`int` to :meth:`__trunc__` is now deprecated. Calling
``int(a)`` when ``type(a)`` implements :meth:`__trunc__` but not
:meth:`__int__` or :meth:`__index__` now raises a :exc:`DeprecationWarning`.
(Contributed by Zackery Spytz in :issue:`44977`.)
* The following have been deprecated in :mod:`configparser` since Python 3.2. * The following have been deprecated in :mod:`configparser` since Python 3.2.
Their deprecation warnings have now been updated to note they will removed in Their deprecation warnings have now been updated to note they will removed in
Python 3.12: Python 3.12:
@ -468,6 +473,7 @@ Deprecated
(Contributed by Hugo van Kemenade in :issue:`45173`.) (Contributed by Hugo van Kemenade in :issue:`45173`.)
Removed Removed
======= =======

View File

@ -2061,7 +2061,6 @@ order (MRO) for bases """
("__format__", format, format_impl, set(), {}), ("__format__", format, format_impl, set(), {}),
("__floor__", math.floor, zero, set(), {}), ("__floor__", math.floor, zero, set(), {}),
("__trunc__", math.trunc, zero, set(), {}), ("__trunc__", math.trunc, zero, set(), {}),
("__trunc__", int, zero, set(), {}),
("__ceil__", math.ceil, zero, set(), {}), ("__ceil__", math.ceil, zero, set(), {}),
("__dir__", dir, empty_seq, set(), {}), ("__dir__", dir, empty_seq, set(), {}),
("__round__", round, zero, set(), {}), ("__round__", round, zero, set(), {}),

View File

@ -369,12 +369,14 @@ class IntTestCases(unittest.TestCase):
class JustTrunc(base): class JustTrunc(base):
def __trunc__(self): def __trunc__(self):
return 42 return 42
self.assertEqual(int(JustTrunc()), 42) with self.assertWarns(DeprecationWarning):
self.assertEqual(int(JustTrunc()), 42)
class ExceptionalTrunc(base): class ExceptionalTrunc(base):
def __trunc__(self): def __trunc__(self):
1 / 0 1 / 0
with self.assertRaises(ZeroDivisionError): with self.assertRaises(ZeroDivisionError), \
self.assertWarns(DeprecationWarning):
int(ExceptionalTrunc()) int(ExceptionalTrunc())
for trunc_result_base in (object, Classic): for trunc_result_base in (object, Classic):
@ -385,7 +387,8 @@ class IntTestCases(unittest.TestCase):
class TruncReturnsNonInt(base): class TruncReturnsNonInt(base):
def __trunc__(self): def __trunc__(self):
return Index() return Index()
self.assertEqual(int(TruncReturnsNonInt()), 42) with self.assertWarns(DeprecationWarning):
self.assertEqual(int(TruncReturnsNonInt()), 42)
class Intable(trunc_result_base): class Intable(trunc_result_base):
def __int__(self): def __int__(self):
@ -394,7 +397,8 @@ class IntTestCases(unittest.TestCase):
class TruncReturnsNonIndex(base): class TruncReturnsNonIndex(base):
def __trunc__(self): def __trunc__(self):
return Intable() return Intable()
self.assertEqual(int(TruncReturnsNonInt()), 42) with self.assertWarns(DeprecationWarning):
self.assertEqual(int(TruncReturnsNonInt()), 42)
class NonIntegral(trunc_result_base): class NonIntegral(trunc_result_base):
def __trunc__(self): def __trunc__(self):
@ -405,7 +409,8 @@ class IntTestCases(unittest.TestCase):
def __trunc__(self): def __trunc__(self):
return NonIntegral() return NonIntegral()
try: try:
int(TruncReturnsNonIntegral()) with self.assertWarns(DeprecationWarning):
int(TruncReturnsNonIntegral())
except TypeError as e: except TypeError as e:
self.assertEqual(str(e), self.assertEqual(str(e),
"__trunc__ returned non-Integral" "__trunc__ returned non-Integral"
@ -423,7 +428,8 @@ class IntTestCases(unittest.TestCase):
def __trunc__(self): def __trunc__(self):
return BadInt() return BadInt()
with self.assertRaises(TypeError): with self.assertRaises(TypeError), \
self.assertWarns(DeprecationWarning):
int(TruncReturnsBadInt()) int(TruncReturnsBadInt())
def test_int_subclass_with_index(self): def test_int_subclass_with_index(self):
@ -517,13 +523,16 @@ class IntTestCases(unittest.TestCase):
self.assertIs(type(n), int) self.assertIs(type(n), int)
bad_int = TruncReturnsBadInt() bad_int = TruncReturnsBadInt()
self.assertRaises(TypeError, int, bad_int) with self.assertWarns(DeprecationWarning):
self.assertRaises(TypeError, int, bad_int)
good_int = TruncReturnsIntSubclass() good_int = TruncReturnsIntSubclass()
n = int(good_int) with self.assertWarns(DeprecationWarning):
n = int(good_int)
self.assertEqual(n, 1) self.assertEqual(n, 1)
self.assertIs(type(n), int) self.assertIs(type(n), int)
n = IntSubclass(good_int) with self.assertWarns(DeprecationWarning):
n = IntSubclass(good_int)
self.assertEqual(n, 1) self.assertEqual(n, 1)
self.assertIs(type(n), IntSubclass) self.assertIs(type(n), IntSubclass)

View File

@ -392,7 +392,8 @@ class LongTest(unittest.TestCase):
return 42 return 42
def __trunc__(self): def __trunc__(self):
return 1729 return 1729
self.assertEqual(int(LongTrunc()), 1729) with self.assertWarns(DeprecationWarning):
self.assertEqual(int(LongTrunc()), 1729)
def check_float_conversion(self, n): def check_float_conversion(self, n):
# Check that int -> float conversion behaviour matches # Check that int -> float conversion behaviour matches

View File

@ -0,0 +1,3 @@
The delegation of :func:`int` to :meth:`__trunc__` is now deprecated.
Calling ``int(a)`` when ``type(a)`` implements :meth:`__trunc__` but not
:meth:`__int__` or :meth:`__index__` now raises a :exc:`DeprecationWarning`.

View File

@ -1564,6 +1564,11 @@ PyNumber_Long(PyObject *o)
} }
trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__); trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__);
if (trunc_func) { if (trunc_func) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"The delegation of int() to __trunc__ is deprecated.", 1)) {
Py_DECREF(trunc_func);
return NULL;
}
result = _PyObject_CallNoArgs(trunc_func); result = _PyObject_CallNoArgs(trunc_func);
Py_DECREF(trunc_func); Py_DECREF(trunc_func);
if (result == NULL || PyLong_CheckExact(result)) { if (result == NULL || PyLong_CheckExact(result)) {