Skip to content

Commit 6a44f6e

Browse files
bpo-36048: Use __index__() instead of __int__() for implicit conversion if available. (GH-11952)
Deprecate using the __int__() method in implicit conversions of Python numbers to C integers.
1 parent d90a141 commit 6a44f6e

File tree

18 files changed

+326
-55
lines changed

18 files changed

+326
-55
lines changed

Doc/c-api/long.rst

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,20 +131,28 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
131131
single: OverflowError (built-in exception)
132132
133133
Return a C :c:type:`long` representation of *obj*. If *obj* is not an
134-
instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method
135-
(if present) to convert it to a :c:type:`PyLongObject`.
134+
instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or
135+
:meth:`__int__` method (if present) to convert it to a
136+
:c:type:`PyLongObject`.
136137
137138
Raise :exc:`OverflowError` if the value of *obj* is out of range for a
138139
:c:type:`long`.
139140
140141
Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
141142
143+
.. versionchanged:: 3.8
144+
Use :meth:`__index__` if available.
145+
146+
.. deprecated:: 3.8
147+
Using :meth:`__int__` is deprecated.
148+
142149
143150
.. c:function:: long PyLong_AsLongAndOverflow(PyObject *obj, int *overflow)
144151
145152
Return a C :c:type:`long` representation of *obj*. If *obj* is not an
146-
instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method
147-
(if present) to convert it to a :c:type:`PyLongObject`.
153+
instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or
154+
:meth:`__int__` method (if present) to convert it to a
155+
:c:type:`PyLongObject`.
148156
149157
If the value of *obj* is greater than :const:`LONG_MAX` or less than
150158
:const:`LONG_MIN`, set *\*overflow* to ``1`` or ``-1``, respectively, and
@@ -153,27 +161,41 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
153161
154162
Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
155163
164+
.. versionchanged:: 3.8
165+
Use :meth:`__index__` if available.
166+
167+
.. deprecated:: 3.8
168+
Using :meth:`__int__` is deprecated.
169+
156170
157171
.. c:function:: long long PyLong_AsLongLong(PyObject *obj)
158172
159173
.. index::
160174
single: OverflowError (built-in exception)
161175
162176
Return a C :c:type:`long long` representation of *obj*. If *obj* is not an
163-
instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method
164-
(if present) to convert it to a :c:type:`PyLongObject`.
177+
instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or
178+
:meth:`__int__` method (if present) to convert it to a
179+
:c:type:`PyLongObject`.
165180
166181
Raise :exc:`OverflowError` if the value of *obj* is out of range for a
167182
:c:type:`long`.
168183
169184
Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
170185
186+
.. versionchanged:: 3.8
187+
Use :meth:`__index__` if available.
188+
189+
.. deprecated:: 3.8
190+
Using :meth:`__int__` is deprecated.
191+
171192
172193
.. c:function:: long long PyLong_AsLongLongAndOverflow(PyObject *obj, int *overflow)
173194
174195
Return a C :c:type:`long long` representation of *obj*. If *obj* is not an
175-
instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method
176-
(if present) to convert it to a :c:type:`PyLongObject`.
196+
instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or
197+
:meth:`__int__` method (if present) to convert it to a
198+
:c:type:`PyLongObject`.
177199
178200
If the value of *obj* is greater than :const:`PY_LLONG_MAX` or less than
179201
:const:`PY_LLONG_MIN`, set *\*overflow* to ``1`` or ``-1``, respectively,
@@ -184,6 +206,12 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
184206
185207
.. versionadded:: 3.2
186208
209+
.. versionchanged:: 3.8
210+
Use :meth:`__index__` if available.
211+
212+
.. deprecated:: 3.8
213+
Using :meth:`__int__` is deprecated.
214+
187215
188216
.. c:function:: Py_ssize_t PyLong_AsSsize_t(PyObject *pylong)
189217
@@ -253,26 +281,40 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
253281
.. c:function:: unsigned long PyLong_AsUnsignedLongMask(PyObject *obj)
254282
255283
Return a C :c:type:`unsigned long` representation of *obj*. If *obj*
256-
is not an instance of :c:type:`PyLongObject`, first call its :meth:`__int__`
257-
method (if present) to convert it to a :c:type:`PyLongObject`.
284+
is not an instance of :c:type:`PyLongObject`, first call its
285+
:meth:`__index__` or :meth:`__int__` method (if present) to convert
286+
it to a :c:type:`PyLongObject`.
258287
259288
If the value of *obj* is out of range for an :c:type:`unsigned long`,
260289
return the reduction of that value modulo ``ULONG_MAX + 1``.
261290
262291
Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
263292
293+
.. versionchanged:: 3.8
294+
Use :meth:`__index__` if available.
295+
296+
.. deprecated:: 3.8
297+
Using :meth:`__int__` is deprecated.
298+
264299
265300
.. c:function:: unsigned long long PyLong_AsUnsignedLongLongMask(PyObject *obj)
266301
267302
Return a C :c:type:`unsigned long long` representation of *obj*. If *obj*
268-
is not an instance of :c:type:`PyLongObject`, first call its :meth:`__int__`
269-
method (if present) to convert it to a :c:type:`PyLongObject`.
303+
is not an instance of :c:type:`PyLongObject`, first call its
304+
:meth:`__index__` or :meth:`__int__` method (if present) to convert
305+
it to a :c:type:`PyLongObject`.
270306
271307
If the value of *obj* is out of range for an :c:type:`unsigned long long`,
272308
return the reduction of that value modulo ``PY_ULLONG_MAX + 1``.
273309
274310
Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
275311
312+
.. versionchanged:: 3.8
313+
Use :meth:`__index__` if available.
314+
315+
.. deprecated:: 3.8
316+
Using :meth:`__int__` is deprecated.
317+
276318
277319
.. c:function:: double PyLong_AsDouble(PyObject *pylong)
278320

Doc/c-api/number.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Number Protocol
1111
Returns ``1`` if the object *o* provides numeric protocols, and false otherwise.
1212
This function always succeeds.
1313
14+
.. versionchanged:: 3.8
15+
Returns ``1`` if *o* is an index integer.
16+
1417
1518
.. c:function:: PyObject* PyNumber_Add(PyObject *o1, PyObject *o2)
1619

Doc/whatsnew/3.8.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,17 @@ Build and C API Changes
458458

459459
(Contributed by Antoine Pitrou in :issue:`32430`.)
460460

461+
* Functions that convert Python number to C integer like
462+
:c:func:`PyLong_AsLong` and argument parsing functions like
463+
:c:func:`PyArg_ParseTuple` with integer converting format units like ``'i'``
464+
will now use the :meth:`~object.__index__` special method instead of
465+
:meth:`~object.__int__`, if available. The deprecation warning will be
466+
emitted for objects with the ``__int__()`` method but without the
467+
``__index__()`` method (like :class:`~decimal.Decimal` and
468+
:class:`~fractions.Fraction`). :c:func:`PyNumber_Check` will now return
469+
``1`` for objects implementing ``__index__()``.
470+
(Contributed by Serhiy Storchaka in :issue:`36048`.)
471+
461472

462473
Deprecated
463474
==========
@@ -508,6 +519,15 @@ Deprecated
508519
* The :meth:`~threading.Thread.isAlive()` method of :class:`threading.Thread` has been deprecated.
509520
(Contributed by Dong-hee Na in :issue:`35283`.)
510521

522+
* Many builtin and extension functions that take integer arguments will
523+
now emit a deprecation warning for :class:`~decimal.Decimal`\ s,
524+
:class:`~fractions.Fraction`\ s and any other objects that can be converted
525+
to integers only with a loss (e.g. that have the :meth:`~object.__int__`
526+
method but do not have the :meth:`~object.__index__` method). In future
527+
version they will be errors.
528+
(Contributed by Serhiy Storchaka in :issue:`36048`.)
529+
530+
511531
API and Feature Removals
512532
========================
513533

Include/longobject.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,17 @@ PyAPI_FUNC(int) _PyLong_AsByteArray(PyLongObject* v,
177177
nb_int slot is not available or the result of the call to nb_int
178178
returns something not of type int.
179179
*/
180-
PyAPI_FUNC(PyLongObject *)_PyLong_FromNbInt(PyObject *);
180+
PyAPI_FUNC(PyObject *) _PyLong_FromNbInt(PyObject *);
181+
182+
/* Convert the given object to a PyLongObject using the nb_index or
183+
nb_int slots, if available (the latter is deprecated).
184+
Raise TypeError if either nb_index and nb_int slots are not
185+
available or the result of the call to nb_index or nb_int
186+
returns something not of type int.
187+
Should be replaced with PyNumber_Index after the end of the
188+
deprecation period.
189+
*/
190+
PyAPI_FUNC(PyObject *) _PyLong_FromNbIndexOrNbInt(PyObject *);
181191

182192
/* _PyLong_Format: Convert the long to a string object with given base,
183193
appending a base prefix of 0[box] if base is 2, 8 or 16. */

Lib/ctypes/test/test_numbers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,18 @@ def __float__(self):
124124
class IntLike(object):
125125
def __int__(self):
126126
return 2
127-
i = IntLike()
127+
d = IntLike()
128+
class IndexLike(object):
129+
def __index__(self):
130+
return 2
131+
i = IndexLike()
128132
# integers cannot be constructed from floats,
129133
# but from integer-like objects
130134
for t in signed_types + unsigned_types:
131135
self.assertRaises(TypeError, t, 3.14)
132136
self.assertRaises(TypeError, t, f)
137+
with self.assertWarns(DeprecationWarning):
138+
self.assertEqual(t(d).value, 2)
133139
self.assertEqual(t(i).value, 2)
134140

135141
def test_sizes(self):

Lib/datetime.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -379,19 +379,34 @@ def _check_utc_offset(name, offset):
379379
def _check_int_field(value):
380380
if isinstance(value, int):
381381
return value
382-
if not isinstance(value, float):
383-
try:
384-
value = value.__int__()
385-
except AttributeError:
386-
pass
387-
else:
388-
if isinstance(value, int):
389-
return value
382+
if isinstance(value, float):
383+
raise TypeError('integer argument expected, got float')
384+
try:
385+
value = value.__index__()
386+
except AttributeError:
387+
pass
388+
else:
389+
if not isinstance(value, int):
390+
raise TypeError('__index__ returned non-int (type %s)' %
391+
type(value).__name__)
392+
return value
393+
orig = value
394+
try:
395+
value = value.__int__()
396+
except AttributeError:
397+
pass
398+
else:
399+
if not isinstance(value, int):
390400
raise TypeError('__int__ returned non-int (type %s)' %
391401
type(value).__name__)
392-
raise TypeError('an integer is required (got type %s)' %
393-
type(value).__name__)
394-
raise TypeError('integer argument expected, got float')
402+
import warnings
403+
warnings.warn("an integer is required (got type %s)" %
404+
type(orig).__name__,
405+
DeprecationWarning,
406+
stacklevel=2)
407+
return value
408+
raise TypeError('an integer is required (got type %s)' %
409+
type(value).__name__)
395410

396411
def _check_date_fields(year, month, day):
397412
year = _check_int_field(year)

Lib/test/datetimetester.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4918,16 +4918,17 @@ def __int__(self):
49184918
for xx in [decimal.Decimal(10),
49194919
decimal.Decimal('10.9'),
49204920
Number(10)]:
4921-
self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4922-
datetime(xx, xx, xx, xx, xx, xx, xx))
4921+
with self.assertWarns(DeprecationWarning):
4922+
self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4923+
datetime(xx, xx, xx, xx, xx, xx, xx))
49234924

49244925
with self.assertRaisesRegex(TypeError, '^an integer is required '
49254926
r'\(got type str\)$'):
49264927
datetime(10, 10, '10')
49274928

49284929
f10 = Number(10.9)
49294930
with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
4930-
r'\(type float\)$'):
4931+
r'\(type float\)$'):
49314932
datetime(10, 10, f10)
49324933

49334934
class Float(float):

Lib/test/test_array.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,8 @@ def test_type_error(self):
12421242
class Intable:
12431243
def __init__(self, num):
12441244
self._num = num
1245+
def __index__(self):
1246+
return self._num
12451247
def __int__(self):
12461248
return self._num
12471249
def __sub__(self, other):

0 commit comments

Comments
 (0)