Skip to content

gh-119740: Remove deprecated trunc delegation #119743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -932,8 +932,7 @@ are always available. They are listed here in alphabetical order.
Return an integer object constructed from a number or string *x*, or return
``0`` if no arguments are given. If *x* defines :meth:`~object.__int__`,
``int(x)`` returns ``x.__int__()``. If *x* defines :meth:`~object.__index__`,
it returns ``x.__index__()``. If *x* defines :meth:`~object.__trunc__`,
it returns ``x.__trunc__()``.
it returns ``x.__index__()``.
For floating point numbers, this truncates towards zero.

If *x* is not a number or if *base* is given, then *x* must be a string,
Expand Down Expand Up @@ -971,9 +970,6 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.8
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
:class:`int` string inputs and string representations can be limited to
help avoid denial of service attacks. A :exc:`ValueError` is raised when
Expand All @@ -982,6 +978,9 @@ are always available. They are listed here in alphabetical order.
See the :ref:`integer string conversion length limitation
<int_max_str_digits>` documentation.

.. versionchanged:: 3.14
:func:`int` no longer delegates to the :meth:`~object.__trunc__` method.

.. function:: isinstance(object, classinfo)

Return ``True`` if the *object* argument is an instance of the *classinfo*
Expand Down
7 changes: 2 additions & 5 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3127,11 +3127,8 @@ left undefined.
return the value of the object truncated to an :class:`~numbers.Integral`
(typically an :class:`int`).

The built-in function :func:`int` falls back to :meth:`__trunc__` if neither
:meth:`__int__` nor :meth:`__index__` is defined.

.. versionchanged:: 3.11
The delegation of :func:`int` to :meth:`__trunc__` is deprecated.
.. versionchanged:: 3.14
:func:`int` no longer delegates to the :meth:`~object.__trunc__` method.


.. _context-managers:
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ Others
It had previously raised a :exc:`DeprecationWarning` since Python 3.9. (Contributed
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
======================
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__subclasscheck__)
STRUCT_FOR_ID(__subclasshook__)
STRUCT_FOR_ID(__truediv__)
STRUCT_FOR_ID(__trunc__)
STRUCT_FOR_ID(__type_params__)
STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
STRUCT_FOR_ID(__typing_prepare_subst__)
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 2 additions & 94 deletions Lib/test/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,68 +402,8 @@ def __trunc__(self):
class JustTrunc(base):
def __trunc__(self):
return 42
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(JustTrunc()), 42)

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())
with self.assertRaises(TypeError):
int(JustTrunc())

def test_int_subclass_with_index(self):
class MyIndex(int):
Expand Down Expand Up @@ -514,18 +454,6 @@ class BadInt2(int):
def __int__(self):
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()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
Expand All @@ -549,26 +477,6 @@ def __trunc__(self):
self.assertEqual(n, 1)
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 check(s, base=None):
with self.assertRaises(ValueError,
Expand Down
9 changes: 0 additions & 9 deletions Lib/test/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,15 +386,6 @@ def __long__(self):
return 42
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):
# Check that int -> float conversion behaviour matches
# that of the pure Python version above.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove the previously-deprecated delegation of :func:`int` to
:meth:`~object.__trunc__`.
32 changes: 0 additions & 32 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -1521,7 +1521,6 @@ PyNumber_Long(PyObject *o)
{
PyObject *result;
PyNumberMethods *m;
PyObject *trunc_func;
Py_buffer view;

if (o == NULL) {
Expand Down Expand Up @@ -1563,37 +1562,6 @@ PyNumber_Long(PyObject *o)
if (m && m->nb_index) {
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))
/* The below check is done in PyLong_FromUnicodeObject(). */
Expand Down