gh-119740: Remove deprecated trunc delegation (#119743)

Remove the delegation of `int` to the `__trunc__` special method: `int` will now only delegate to `__int__` and `__index__` (in that order). `__trunc__` continues to exist, but its sole purpose is to support `math.trunc`.

---------

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Mark Dickinson 2024-06-02 10:16:49 +01:00 committed by GitHub
parent 4aed319a8e
commit f79ffc879b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 16 additions and 152 deletions

View File

@ -1004,9 +1004,8 @@ are always available. They are listed here in alphabetical order.
115 115
If the argument defines :meth:`~object.__int__`, If the argument defines :meth:`~object.__int__`,
``int(x)`` returns ``x.__int__()``. If the argument defines :meth:`~object.__index__`, ``int(x)`` returns ``x.__int__()``. If the argument defines
it returns ``x.__index__()``. If the argument defines :meth:`~object.__trunc__`, :meth:`~object.__index__`, it returns ``x.__index__()``.
it returns ``x.__trunc__()``.
For floating point numbers, this truncates towards zero. For floating point numbers, this truncates towards zero.
If the argument is not a number or if *base* is given, then it must be a string, If the argument is not a number or if *base* is given, then it must be a string,
@ -1044,9 +1043,6 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.8 .. versionchanged:: 3.8
Falls back to :meth:`~object.__index__` if :meth:`~object.__int__` is not defined. Falls back to :meth:`~object.__index__` if :meth:`~object.__int__` is not defined.
.. versionchanged:: 3.11
The delegation to :meth:`~object.__trunc__` is deprecated.
.. versionchanged:: 3.11 .. versionchanged:: 3.11
:class:`int` string inputs and string representations can be limited to :class:`int` string inputs and string representations can be limited to
help avoid denial of service attacks. A :exc:`ValueError` is raised when help avoid denial of service attacks. A :exc:`ValueError` is raised when
@ -1055,6 +1051,9 @@ are always available. They are listed here in alphabetical order.
See the :ref:`integer string conversion length limitation See the :ref:`integer string conversion length limitation
<int_max_str_digits>` documentation. <int_max_str_digits>` documentation.
.. versionchanged:: 3.14
:func:`int` no longer delegates to the :meth:`~object.__trunc__` method.
.. function:: isinstance(object, classinfo) .. function:: isinstance(object, classinfo)
Return ``True`` if the *object* argument is an instance of the *classinfo* Return ``True`` if the *object* argument is an instance of the *classinfo*

View File

@ -3127,11 +3127,8 @@ left undefined.
return the value of the object truncated to an :class:`~numbers.Integral` return the value of the object truncated to an :class:`~numbers.Integral`
(typically an :class:`int`). (typically an :class:`int`).
The built-in function :func:`int` falls back to :meth:`__trunc__` if neither .. versionchanged:: 3.14
:meth:`__int__` nor :meth:`__index__` is defined. :func:`int` no longer delegates to the :meth:`~object.__trunc__` method.
.. versionchanged:: 3.11
The delegation of :func:`int` to :meth:`__trunc__` is deprecated.
.. _context-managers: .. _context-managers:

View File

@ -225,6 +225,11 @@ Others
It had previously raised a :exc:`DeprecationWarning` since Python 3.9. (Contributed It had previously raised a :exc:`DeprecationWarning` since Python 3.9. (Contributed
by Jelle Zijlstra in :gh:`118767`.) by Jelle Zijlstra in :gh:`118767`.)
* The :func:`int` built-in no longer delegates to
:meth:`~object.__trunc__`. Classes that want to support conversion to
integer must implement either :meth:`~object.__int__` or
:meth:`~object.__index__`. (Contributed by Mark Dickinson in :gh:`119743`.)
Porting to Python 3.14 Porting to Python 3.14
====================== ======================

View File

@ -732,7 +732,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasscheck__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasscheck__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasshook__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasshook__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__truediv__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__truediv__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__trunc__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__type_params__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__type_params__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__typing_is_unpacked_typevartuple__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__typing_is_unpacked_typevartuple__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__typing_prepare_subst__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__typing_prepare_subst__));

View File

@ -221,7 +221,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__subclasscheck__) STRUCT_FOR_ID(__subclasscheck__)
STRUCT_FOR_ID(__subclasshook__) STRUCT_FOR_ID(__subclasshook__)
STRUCT_FOR_ID(__truediv__) STRUCT_FOR_ID(__truediv__)
STRUCT_FOR_ID(__trunc__)
STRUCT_FOR_ID(__type_params__) STRUCT_FOR_ID(__type_params__)
STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__) STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
STRUCT_FOR_ID(__typing_prepare_subst__) STRUCT_FOR_ID(__typing_prepare_subst__)

View File

@ -730,7 +730,6 @@ extern "C" {
INIT_ID(__subclasscheck__), \ INIT_ID(__subclasscheck__), \
INIT_ID(__subclasshook__), \ INIT_ID(__subclasshook__), \
INIT_ID(__truediv__), \ INIT_ID(__truediv__), \
INIT_ID(__trunc__), \
INIT_ID(__type_params__), \ INIT_ID(__type_params__), \
INIT_ID(__typing_is_unpacked_typevartuple__), \ INIT_ID(__typing_is_unpacked_typevartuple__), \
INIT_ID(__typing_prepare_subst__), \ INIT_ID(__typing_prepare_subst__), \

View File

@ -504,9 +504,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(__truediv__); string = &_Py_ID(__truediv__);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string); _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(__trunc__);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(__type_params__); string = &_Py_ID(__type_params__);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string); _PyUnicode_InternInPlace(interp, &string);

View File

@ -402,68 +402,8 @@ class IntTestCases(unittest.TestCase):
class JustTrunc(base): class JustTrunc(base):
def __trunc__(self): def __trunc__(self):
return 42 return 42
with self.assertWarns(DeprecationWarning): with self.assertRaises(TypeError):
self.assertEqual(int(JustTrunc()), 42) int(JustTrunc())
class ExceptionalTrunc(base):
def __trunc__(self):
1 / 0
with self.assertRaises(ZeroDivisionError), \
self.assertWarns(DeprecationWarning):
int(ExceptionalTrunc())
for trunc_result_base in (object, Classic):
class Index(trunc_result_base):
def __index__(self):
return 42
class TruncReturnsNonInt(base):
def __trunc__(self):
return Index()
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(TruncReturnsNonInt()), 42)
class Intable(trunc_result_base):
def __int__(self):
return 42
class TruncReturnsNonIndex(base):
def __trunc__(self):
return Intable()
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(TruncReturnsNonInt()), 42)
class NonIntegral(trunc_result_base):
def __trunc__(self):
# Check that we avoid infinite recursion.
return NonIntegral()
class TruncReturnsNonIntegral(base):
def __trunc__(self):
return NonIntegral()
try:
with self.assertWarns(DeprecationWarning):
int(TruncReturnsNonIntegral())
except TypeError as e:
self.assertEqual(str(e),
"__trunc__ returned non-Integral"
" (type NonIntegral)")
else:
self.fail("Failed to raise TypeError with %s" %
((base, trunc_result_base),))
# Regression test for bugs.python.org/issue16060.
class BadInt(trunc_result_base):
def __int__(self):
return 42.0
class TruncReturnsBadInt(base):
def __trunc__(self):
return BadInt()
with self.assertRaises(TypeError), \
self.assertWarns(DeprecationWarning):
int(TruncReturnsBadInt())
def test_int_subclass_with_index(self): def test_int_subclass_with_index(self):
class MyIndex(int): class MyIndex(int):
@ -514,18 +454,6 @@ class IntTestCases(unittest.TestCase):
def __int__(self): def __int__(self):
return True return True
class TruncReturnsBadIndex:
def __trunc__(self):
return BadIndex()
class TruncReturnsBadInt:
def __trunc__(self):
return BadInt()
class TruncReturnsIntSubclass:
def __trunc__(self):
return True
bad_int = BadIndex() bad_int = BadIndex()
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
n = int(bad_int) n = int(bad_int)
@ -549,26 +477,6 @@ class IntTestCases(unittest.TestCase):
self.assertEqual(n, 1) self.assertEqual(n, 1)
self.assertIs(type(n), int) self.assertIs(type(n), int)
bad_int = TruncReturnsBadIndex()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
self.assertEqual(n, 1)
self.assertIs(type(n), int)
bad_int = TruncReturnsBadInt()
with self.assertWarns(DeprecationWarning):
self.assertRaises(TypeError, int, bad_int)
good_int = TruncReturnsIntSubclass()
with self.assertWarns(DeprecationWarning):
n = int(good_int)
self.assertEqual(n, 1)
self.assertIs(type(n), int)
with self.assertWarns(DeprecationWarning):
n = IntSubclass(good_int)
self.assertEqual(n, 1)
self.assertIs(type(n), IntSubclass)
def test_error_message(self): def test_error_message(self):
def check(s, base=None): def check(s, base=None):
with self.assertRaises(ValueError, with self.assertRaises(ValueError,

View File

@ -386,15 +386,6 @@ class LongTest(unittest.TestCase):
return 42 return 42
self.assertRaises(TypeError, int, JustLong()) self.assertRaises(TypeError, int, JustLong())
class LongTrunc:
# __long__ should be ignored in 3.x
def __long__(self):
return 42
def __trunc__(self):
return 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
# that of the pure Python version above. # that of the pure Python version above.

View File

@ -0,0 +1,2 @@
Remove the previously-deprecated delegation of :func:`int` to
:meth:`~object.__trunc__`.

View File

@ -1521,7 +1521,6 @@ PyNumber_Long(PyObject *o)
{ {
PyObject *result; PyObject *result;
PyNumberMethods *m; PyNumberMethods *m;
PyObject *trunc_func;
Py_buffer view; Py_buffer view;
if (o == NULL) { if (o == NULL) {
@ -1563,37 +1562,6 @@ PyNumber_Long(PyObject *o)
if (m && m->nb_index) { if (m && m->nb_index) {
return PyNumber_Index(o); return PyNumber_Index(o);
} }
trunc_func = _PyObject_LookupSpecial(o, &_Py_ID(__trunc__));
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);
Py_DECREF(trunc_func);
if (result == NULL || PyLong_CheckExact(result)) {
return result;
}
if (PyLong_Check(result)) {
Py_SETREF(result, _PyLong_Copy((PyLongObject *)result));
return result;
}
/* __trunc__ is specified to return an Integral type,
but int() needs to return an int. */
if (!PyIndex_Check(result)) {
PyErr_Format(
PyExc_TypeError,
"__trunc__ returned non-Integral (type %.200s)",
Py_TYPE(result)->tp_name);
Py_DECREF(result);
return NULL;
}
Py_SETREF(result, PyNumber_Index(result));
return result;
}
if (PyErr_Occurred())
return NULL;
if (PyUnicode_Check(o)) if (PyUnicode_Check(o))
/* The below check is done in PyLong_FromUnicodeObject(). */ /* The below check is done in PyLong_FromUnicodeObject(). */