From 6a44f6eef3d0958d88882347190b3e2d1222c2e9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 25 Feb 2019 17:57:58 +0200 Subject: [PATCH] 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. --- Doc/c-api/long.rst | 66 +++++++++-- Doc/c-api/number.rst | 3 + Doc/whatsnew/3.8.rst | 20 ++++ Include/longobject.h | 12 +- Lib/ctypes/test/test_numbers.py | 8 +- Lib/datetime.py | 37 ++++-- Lib/test/datetimetester.py | 7 +- Lib/test/test_array.py | 2 + Lib/test/test_getargs2.py | 109 ++++++++++++++++-- Lib/test/test_int.py | 3 +- Lib/test/test_zlib.py | 2 +- .../2019-02-20-08-51-04.bpo-36048.I3LJt9.rst | 4 + Modules/arraymodule.c | 2 +- Modules/zlibmodule.c | 4 +- Objects/abstract.c | 11 +- Objects/longobject.c | 85 ++++++++++++-- Python/getargs.c | 4 +- Tools/clinic/clinic.py | 2 + 18 files changed, 326 insertions(+), 55 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 5b1f386fb7e..8093f4b627a 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -131,20 +131,28 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. single: OverflowError (built-in exception) Return a C :c:type:`long` representation of *obj*. If *obj* is not an - instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method - (if present) to convert it to a :c:type:`PyLongObject`. + instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or + :meth:`__int__` method (if present) to convert it to a + :c:type:`PyLongObject`. Raise :exc:`OverflowError` if the value of *obj* is out of range for a :c:type:`long`. Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: long PyLong_AsLongAndOverflow(PyObject *obj, int *overflow) Return a C :c:type:`long` representation of *obj*. If *obj* is not an - instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method - (if present) to convert it to a :c:type:`PyLongObject`. + instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or + :meth:`__int__` method (if present) to convert it to a + :c:type:`PyLongObject`. If the value of *obj* is greater than :const:`LONG_MAX` or less than :const:`LONG_MIN`, set *\*overflow* to ``1`` or ``-1``, respectively, and @@ -153,6 +161,12 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: long long PyLong_AsLongLong(PyObject *obj) @@ -160,20 +174,28 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. single: OverflowError (built-in exception) Return a C :c:type:`long long` representation of *obj*. If *obj* is not an - instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method - (if present) to convert it to a :c:type:`PyLongObject`. + instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or + :meth:`__int__` method (if present) to convert it to a + :c:type:`PyLongObject`. Raise :exc:`OverflowError` if the value of *obj* is out of range for a :c:type:`long`. Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: long long PyLong_AsLongLongAndOverflow(PyObject *obj, int *overflow) Return a C :c:type:`long long` representation of *obj*. If *obj* is not an - instance of :c:type:`PyLongObject`, first call its :meth:`__int__` method - (if present) to convert it to a :c:type:`PyLongObject`. + instance of :c:type:`PyLongObject`, first call its :meth:`__index__` or + :meth:`__int__` method (if present) to convert it to a + :c:type:`PyLongObject`. If the value of *obj* is greater than :const:`PY_LLONG_MAX` or less than :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. .. versionadded:: 3.2 + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: Py_ssize_t PyLong_AsSsize_t(PyObject *pylong) @@ -253,26 +281,40 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. c:function:: unsigned long PyLong_AsUnsignedLongMask(PyObject *obj) Return a C :c:type:`unsigned long` representation of *obj*. If *obj* - is not an instance of :c:type:`PyLongObject`, first call its :meth:`__int__` - method (if present) to convert it to a :c:type:`PyLongObject`. + is not an instance of :c:type:`PyLongObject`, first call its + :meth:`__index__` or :meth:`__int__` method (if present) to convert + it to a :c:type:`PyLongObject`. If the value of *obj* is out of range for an :c:type:`unsigned long`, return the reduction of that value modulo ``ULONG_MAX + 1``. Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: unsigned long long PyLong_AsUnsignedLongLongMask(PyObject *obj) Return a C :c:type:`unsigned long long` representation of *obj*. If *obj* - is not an instance of :c:type:`PyLongObject`, first call its :meth:`__int__` - method (if present) to convert it to a :c:type:`PyLongObject`. + is not an instance of :c:type:`PyLongObject`, first call its + :meth:`__index__` or :meth:`__int__` method (if present) to convert + it to a :c:type:`PyLongObject`. If the value of *obj* is out of range for an :c:type:`unsigned long long`, return the reduction of that value modulo ``PY_ULLONG_MAX + 1``. Returns ``-1`` on error. Use :c:func:`PyErr_Occurred` to disambiguate. + .. versionchanged:: 3.8 + Use :meth:`__index__` if available. + + .. deprecated:: 3.8 + Using :meth:`__int__` is deprecated. + .. c:function:: double PyLong_AsDouble(PyObject *pylong) diff --git a/Doc/c-api/number.rst b/Doc/c-api/number.rst index 296b21c132b..9fb220b192b 100644 --- a/Doc/c-api/number.rst +++ b/Doc/c-api/number.rst @@ -11,6 +11,9 @@ Number Protocol Returns ``1`` if the object *o* provides numeric protocols, and false otherwise. This function always succeeds. + .. versionchanged:: 3.8 + Returns ``1`` if *o* is an index integer. + .. c:function:: PyObject* PyNumber_Add(PyObject *o1, PyObject *o2) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 68a4457106b..154fd66630e 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -458,6 +458,17 @@ Build and C API Changes (Contributed by Antoine Pitrou in :issue:`32430`.) +* Functions that convert Python number to C integer like + :c:func:`PyLong_AsLong` and argument parsing functions like + :c:func:`PyArg_ParseTuple` with integer converting format units like ``'i'`` + will now use the :meth:`~object.__index__` special method instead of + :meth:`~object.__int__`, if available. The deprecation warning will be + emitted for objects with the ``__int__()`` method but without the + ``__index__()`` method (like :class:`~decimal.Decimal` and + :class:`~fractions.Fraction`). :c:func:`PyNumber_Check` will now return + ``1`` for objects implementing ``__index__()``. + (Contributed by Serhiy Storchaka in :issue:`36048`.) + Deprecated ========== @@ -508,6 +519,15 @@ Deprecated * The :meth:`~threading.Thread.isAlive()` method of :class:`threading.Thread` has been deprecated. (Contributed by Dong-hee Na in :issue:`35283`.) +* Many builtin and extension functions that take integer arguments will + now emit a deprecation warning for :class:`~decimal.Decimal`\ s, + :class:`~fractions.Fraction`\ s and any other objects that can be converted + to integers only with a loss (e.g. that have the :meth:`~object.__int__` + method but do not have the :meth:`~object.__index__` method). In future + version they will be errors. + (Contributed by Serhiy Storchaka in :issue:`36048`.) + + API and Feature Removals ======================== diff --git a/Include/longobject.h b/Include/longobject.h index 82c06c92a63..b696f544b9c 100644 --- a/Include/longobject.h +++ b/Include/longobject.h @@ -177,7 +177,17 @@ PyAPI_FUNC(int) _PyLong_AsByteArray(PyLongObject* v, nb_int slot is not available or the result of the call to nb_int returns something not of type int. */ -PyAPI_FUNC(PyLongObject *)_PyLong_FromNbInt(PyObject *); +PyAPI_FUNC(PyObject *) _PyLong_FromNbInt(PyObject *); + +/* Convert the given object to a PyLongObject using the nb_index or + nb_int slots, if available (the latter is deprecated). + Raise TypeError if either nb_index and nb_int slots are not + available or the result of the call to nb_index or nb_int + returns something not of type int. + Should be replaced with PyNumber_Index after the end of the + deprecation period. +*/ +PyAPI_FUNC(PyObject *) _PyLong_FromNbIndexOrNbInt(PyObject *); /* _PyLong_Format: Convert the long to a string object with given base, appending a base prefix of 0[box] if base is 2, 8 or 16. */ diff --git a/Lib/ctypes/test/test_numbers.py b/Lib/ctypes/test/test_numbers.py index 09eef90f748..c6d843b2cd9 100644 --- a/Lib/ctypes/test/test_numbers.py +++ b/Lib/ctypes/test/test_numbers.py @@ -124,12 +124,18 @@ class NumberTestCase(unittest.TestCase): class IntLike(object): def __int__(self): return 2 - i = IntLike() + d = IntLike() + class IndexLike(object): + def __index__(self): + return 2 + i = IndexLike() # integers cannot be constructed from floats, # but from integer-like objects for t in signed_types + unsigned_types: self.assertRaises(TypeError, t, 3.14) self.assertRaises(TypeError, t, f) + with self.assertWarns(DeprecationWarning): + self.assertEqual(t(d).value, 2) self.assertEqual(t(i).value, 2) def test_sizes(self): diff --git a/Lib/datetime.py b/Lib/datetime.py index 89c32c0b0a6..85bfa48e05d 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -379,19 +379,34 @@ def _check_utc_offset(name, offset): def _check_int_field(value): if isinstance(value, int): return value - if not isinstance(value, float): - try: - value = value.__int__() - except AttributeError: - pass - else: - if isinstance(value, int): - return value + if isinstance(value, float): + raise TypeError('integer argument expected, got float') + try: + value = value.__index__() + except AttributeError: + pass + else: + if not isinstance(value, int): + raise TypeError('__index__ returned non-int (type %s)' % + type(value).__name__) + return value + orig = value + try: + value = value.__int__() + except AttributeError: + pass + else: + if not isinstance(value, int): raise TypeError('__int__ returned non-int (type %s)' % type(value).__name__) - raise TypeError('an integer is required (got type %s)' % - type(value).__name__) - raise TypeError('integer argument expected, got float') + import warnings + warnings.warn("an integer is required (got type %s)" % + type(orig).__name__, + DeprecationWarning, + stacklevel=2) + return value + raise TypeError('an integer is required (got type %s)' % + type(value).__name__) def _check_date_fields(year, month, day): year = _check_int_field(year) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 958b33675c3..715f0ea6b40 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -4918,8 +4918,9 @@ class Oddballs(unittest.TestCase): for xx in [decimal.Decimal(10), decimal.Decimal('10.9'), Number(10)]: - self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10), - datetime(xx, xx, xx, xx, xx, xx, xx)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10), + datetime(xx, xx, xx, xx, xx, xx, xx)) with self.assertRaisesRegex(TypeError, '^an integer is required ' r'\(got type str\)$'): @@ -4927,7 +4928,7 @@ class Oddballs(unittest.TestCase): f10 = Number(10.9) with self.assertRaisesRegex(TypeError, '^__int__ returned non-int ' - r'\(type float\)$'): + r'\(type float\)$'): datetime(10, 10, f10) class Float(float): diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 7f402f86447..2399980d134 100644 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1242,6 +1242,8 @@ class IntegerNumberTest(NumberTest): class Intable: def __init__(self, num): self._num = num + def __index__(self): + return self._num def __int__(self): return self._num def __sub__(self, other): diff --git a/Lib/test/test_getargs2.py b/Lib/test/test_getargs2.py index d9bcb1d75bd..07e2d151379 100644 --- a/Lib/test/test_getargs2.py +++ b/Lib/test/test_getargs2.py @@ -53,6 +53,27 @@ LLONG_MAX = 2**63-1 LLONG_MIN = -2**63 ULLONG_MAX = 2**64-1 +class Index: + def __index__(self): + return 99 + +class IndexIntSubclass(int): + def __index__(self): + return 99 + +class BadIndex: + def __index__(self): + return 1.0 + +class BadIndex2: + def __index__(self): + return True + +class BadIndex3(int): + def __index__(self): + return True + + class Int: def __int__(self): return 99 @@ -134,7 +155,14 @@ class Unsigned_TestCase(unittest.TestCase): from _testcapi import getargs_b # b returns 'unsigned char', and does range checking (0 ... UCHAR_MAX) self.assertRaises(TypeError, getargs_b, 3.14) - self.assertEqual(99, getargs_b(Int())) + self.assertEqual(99, getargs_b(Index())) + self.assertEqual(0, getargs_b(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_b, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_b(BadIndex2())) + self.assertEqual(0, getargs_b(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_b(Int())) self.assertEqual(0, getargs_b(IntSubclass())) self.assertRaises(TypeError, getargs_b, BadInt()) with self.assertWarns(DeprecationWarning): @@ -153,7 +181,14 @@ class Unsigned_TestCase(unittest.TestCase): from _testcapi import getargs_B # B returns 'unsigned char', no range checking self.assertRaises(TypeError, getargs_B, 3.14) - self.assertEqual(99, getargs_B(Int())) + self.assertEqual(99, getargs_B(Index())) + self.assertEqual(0, getargs_B(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_B, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_B(BadIndex2())) + self.assertEqual(0, getargs_B(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_B(Int())) self.assertEqual(0, getargs_B(IntSubclass())) self.assertRaises(TypeError, getargs_B, BadInt()) with self.assertWarns(DeprecationWarning): @@ -172,7 +207,14 @@ class Unsigned_TestCase(unittest.TestCase): from _testcapi import getargs_H # H returns 'unsigned short', no range checking self.assertRaises(TypeError, getargs_H, 3.14) - self.assertEqual(99, getargs_H(Int())) + self.assertEqual(99, getargs_H(Index())) + self.assertEqual(0, getargs_H(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_H, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_H(BadIndex2())) + self.assertEqual(0, getargs_H(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_H(Int())) self.assertEqual(0, getargs_H(IntSubclass())) self.assertRaises(TypeError, getargs_H, BadInt()) with self.assertWarns(DeprecationWarning): @@ -192,7 +234,14 @@ class Unsigned_TestCase(unittest.TestCase): from _testcapi import getargs_I # I returns 'unsigned int', no range checking self.assertRaises(TypeError, getargs_I, 3.14) - self.assertEqual(99, getargs_I(Int())) + self.assertEqual(99, getargs_I(Index())) + self.assertEqual(0, getargs_I(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_I, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_I(BadIndex2())) + self.assertEqual(0, getargs_I(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_I(Int())) self.assertEqual(0, getargs_I(IntSubclass())) self.assertRaises(TypeError, getargs_I, BadInt()) with self.assertWarns(DeprecationWarning): @@ -213,6 +262,11 @@ class Unsigned_TestCase(unittest.TestCase): # k returns 'unsigned long', no range checking # it does not accept float, or instances with __int__ self.assertRaises(TypeError, getargs_k, 3.14) + self.assertRaises(TypeError, getargs_k, Index()) + self.assertEqual(0, getargs_k(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_k, BadIndex()) + self.assertRaises(TypeError, getargs_k, BadIndex2()) + self.assertEqual(0, getargs_k(BadIndex3())) self.assertRaises(TypeError, getargs_k, Int()) self.assertEqual(0, getargs_k(IntSubclass())) self.assertRaises(TypeError, getargs_k, BadInt()) @@ -233,7 +287,14 @@ class Signed_TestCase(unittest.TestCase): from _testcapi import getargs_h # h returns 'short', and does range checking (SHRT_MIN ... SHRT_MAX) self.assertRaises(TypeError, getargs_h, 3.14) - self.assertEqual(99, getargs_h(Int())) + self.assertEqual(99, getargs_h(Index())) + self.assertEqual(0, getargs_h(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_h, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_h(BadIndex2())) + self.assertEqual(0, getargs_h(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_h(Int())) self.assertEqual(0, getargs_h(IntSubclass())) self.assertRaises(TypeError, getargs_h, BadInt()) with self.assertWarns(DeprecationWarning): @@ -252,7 +313,14 @@ class Signed_TestCase(unittest.TestCase): from _testcapi import getargs_i # i returns 'int', and does range checking (INT_MIN ... INT_MAX) self.assertRaises(TypeError, getargs_i, 3.14) - self.assertEqual(99, getargs_i(Int())) + self.assertEqual(99, getargs_i(Index())) + self.assertEqual(0, getargs_i(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_i, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_i(BadIndex2())) + self.assertEqual(0, getargs_i(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_i(Int())) self.assertEqual(0, getargs_i(IntSubclass())) self.assertRaises(TypeError, getargs_i, BadInt()) with self.assertWarns(DeprecationWarning): @@ -271,7 +339,14 @@ class Signed_TestCase(unittest.TestCase): from _testcapi import getargs_l # l returns 'long', and does range checking (LONG_MIN ... LONG_MAX) self.assertRaises(TypeError, getargs_l, 3.14) - self.assertEqual(99, getargs_l(Int())) + self.assertEqual(99, getargs_l(Index())) + self.assertEqual(0, getargs_l(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_l, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_l(BadIndex2())) + self.assertEqual(0, getargs_l(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_l(Int())) self.assertEqual(0, getargs_l(IntSubclass())) self.assertRaises(TypeError, getargs_l, BadInt()) with self.assertWarns(DeprecationWarning): @@ -291,6 +366,12 @@ class Signed_TestCase(unittest.TestCase): # n returns 'Py_ssize_t', and does range checking # (PY_SSIZE_T_MIN ... PY_SSIZE_T_MAX) self.assertRaises(TypeError, getargs_n, 3.14) + self.assertEqual(99, getargs_n(Index())) + self.assertEqual(0, getargs_n(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_n, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_n(BadIndex2())) + self.assertEqual(0, getargs_n(BadIndex3())) self.assertRaises(TypeError, getargs_n, Int()) self.assertEqual(0, getargs_n(IntSubclass())) self.assertRaises(TypeError, getargs_n, BadInt()) @@ -313,7 +394,14 @@ class LongLong_TestCase(unittest.TestCase): # ... LLONG_MAX) self.assertRaises(TypeError, getargs_L, 3.14) self.assertRaises(TypeError, getargs_L, "Hello") - self.assertEqual(99, getargs_L(Int())) + self.assertEqual(99, getargs_L(Index())) + self.assertEqual(0, getargs_L(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_L, BadIndex()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(1, getargs_L(BadIndex2())) + self.assertEqual(0, getargs_L(BadIndex3())) + with self.assertWarns(DeprecationWarning): + self.assertEqual(99, getargs_L(Int())) self.assertEqual(0, getargs_L(IntSubclass())) self.assertRaises(TypeError, getargs_L, BadInt()) with self.assertWarns(DeprecationWarning): @@ -332,6 +420,11 @@ class LongLong_TestCase(unittest.TestCase): from _testcapi import getargs_K # K return 'unsigned long long', no range checking self.assertRaises(TypeError, getargs_K, 3.14) + self.assertRaises(TypeError, getargs_K, Index()) + self.assertEqual(0, getargs_K(IndexIntSubclass())) + self.assertRaises(TypeError, getargs_K, BadIndex()) + self.assertRaises(TypeError, getargs_K, BadIndex2()) + self.assertEqual(0, getargs_K(BadIndex3())) self.assertRaises(TypeError, getargs_K, Int()) self.assertEqual(0, getargs_K(IntSubclass())) self.assertRaises(TypeError, getargs_K, BadInt()) diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index c048b712da8..307ca36bb4f 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -385,7 +385,8 @@ class IntTestCases(unittest.TestCase): class TruncReturnsNonInt(base): def __trunc__(self): return Integral() - self.assertEqual(int(TruncReturnsNonInt()), 42) + with self.assertWarns(DeprecationWarning): + self.assertEqual(int(TruncReturnsNonInt()), 42) class NonIntegral(trunc_result_base): def __trunc__(self): diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index bf5d64ceb29..f828b4c737a 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -914,7 +914,7 @@ LAERTES class CustomInt: - def __int__(self): + def __index__(self): return 100 diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst new file mode 100644 index 00000000000..d032e84341c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst @@ -0,0 +1,4 @@ +The :meth:`~object.__index__` special method will be used instead of +:meth:`~object.__int__` for implicit conversion of Python numbers to C +integers. Using the ``__int__()`` method in implicit conversions has been +deprecated. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index f5f461b7564..a5ba27cb36e 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -340,7 +340,7 @@ get_int_unless_float(PyObject *v) "array item must be integer"); return NULL; } - return (PyObject *)_PyLong_FromNbInt(v); + return _PyLong_FromNbIndexOrNbInt(v); } static int diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 00bbe21fc0b..5778dbb715f 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -291,7 +291,9 @@ ssize_t_converter(PyObject *obj, void *ptr) PyObject *long_obj; Py_ssize_t val; - long_obj = (PyObject *)_PyLong_FromNbInt(obj); + /* XXX Should be replaced with PyNumber_AsSsize_t after the end of the + deprecation period. */ + long_obj = _PyLong_FromNbIndexOrNbInt(obj); if (long_obj == NULL) { return 0; } diff --git a/Objects/abstract.c b/Objects/abstract.c index 0565ba34f5b..68d06edfa60 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -759,8 +759,9 @@ int PyNumber_Check(PyObject *o) { return o && o->ob_type->tp_as_number && - (o->ob_type->tp_as_number->nb_int || - o->ob_type->tp_as_number->nb_float); + (o->ob_type->tp_as_number->nb_index || + o->ob_type->tp_as_number->nb_int || + o->ob_type->tp_as_number->nb_float); } /* Binary operators */ @@ -1366,7 +1367,7 @@ PyNumber_Long(PyObject *o) } m = o->ob_type->tp_as_number; if (m && m->nb_int) { /* This should include subclasses of int */ - result = (PyObject *)_PyLong_FromNbInt(o); + result = _PyLong_FromNbInt(o); if (result != NULL && !PyLong_CheckExact(result)) { Py_SETREF(result, _PyLong_Copy((PyLongObject *)result)); } @@ -1386,7 +1387,7 @@ PyNumber_Long(PyObject *o) /* __trunc__ is specified to return an Integral type, but int() needs to return an int. */ m = result->ob_type->tp_as_number; - if (m == NULL || m->nb_int == NULL) { + if (m == NULL || (m->nb_index == NULL && m->nb_int == NULL)) { PyErr_Format( PyExc_TypeError, "__trunc__ returned non-Integral (type %.200s)", @@ -1394,7 +1395,7 @@ PyNumber_Long(PyObject *o) Py_DECREF(result); return NULL; } - Py_SETREF(result, (PyObject *)_PyLong_FromNbInt(result)); + Py_SETREF(result, _PyLong_FromNbIndexOrNbInt(result)); if (result != NULL && !PyLong_CheckExact(result)) { Py_SETREF(result, _PyLong_Copy((PyLongObject *)result)); } diff --git a/Objects/longobject.c b/Objects/longobject.c index d7b01cebac8..1e3445c64a6 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -140,7 +140,7 @@ long_normalize(PyLongObject *v) nb_int slot is not available or the result of the call to nb_int returns something not of type int. */ -PyLongObject * +PyObject * _PyLong_FromNbInt(PyObject *integral) { PyNumberMethods *nb; @@ -149,7 +149,7 @@ _PyLong_FromNbInt(PyObject *integral) /* Fast path for the case that we already have an int. */ if (PyLong_CheckExact(integral)) { Py_INCREF(integral); - return (PyLongObject *)integral; + return integral; } nb = Py_TYPE(integral)->tp_as_number; @@ -164,7 +164,7 @@ _PyLong_FromNbInt(PyObject *integral) of exact type int. */ result = nb->nb_int(integral); if (!result || PyLong_CheckExact(result)) - return (PyLongObject *)result; + return result; if (!PyLong_Check(result)) { PyErr_Format(PyExc_TypeError, "__int__ returned non-int (type %.200s)", @@ -181,7 +181,74 @@ _PyLong_FromNbInt(PyObject *integral) Py_DECREF(result); return NULL; } - return (PyLongObject *)result; + return result; +} + +/* Convert the given object to a PyLongObject using the nb_index or + nb_int slots, if available (the latter is deprecated). + Raise TypeError if either nb_index and nb_int slots are not + available or the result of the call to nb_index or nb_int + returns something not of type int. + Should be replaced with PyNumber_Index after the end of the + deprecation period. +*/ +PyObject * +_PyLong_FromNbIndexOrNbInt(PyObject *integral) +{ + PyNumberMethods *nb; + PyObject *result; + + /* Fast path for the case that we already have an int. */ + if (PyLong_CheckExact(integral)) { + Py_INCREF(integral); + return integral; + } + + nb = Py_TYPE(integral)->tp_as_number; + if (nb == NULL || (nb->nb_index == NULL && nb->nb_int == NULL)) { + PyErr_Format(PyExc_TypeError, + "an integer is required (got type %.200s)", + Py_TYPE(integral)->tp_name); + return NULL; + } + + if (nb->nb_index) { + /* Convert using the nb_index slot, which should return something + of exact type int. */ + result = nb->nb_index(integral); + if (!result || PyLong_CheckExact(result)) + return result; + if (!PyLong_Check(result)) { + PyErr_Format(PyExc_TypeError, + "__index__ returned non-int (type %.200s)", + result->ob_type->tp_name); + Py_DECREF(result); + return NULL; + } + /* Issue #17576: warn if 'result' not of exact type int. */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "__index__ returned non-int (type %.200s). " + "The ability to return an instance of a strict subclass of int " + "is deprecated, and may be removed in a future version of Python.", + result->ob_type->tp_name)) + { + Py_DECREF(result); + return NULL; + } + return result; + } + + result = _PyLong_FromNbInt(integral); + if (result && PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "an integer is required (got type %.200s). " + "Implicit conversion to integers using __int__ is deprecated, " + "and may be removed in a future version of Python.", + Py_TYPE(integral)->tp_name)) + { + Py_DECREF(result); + return NULL; + } + return result; } @@ -420,7 +487,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) v = (PyLongObject *)vv; } else { - v = _PyLong_FromNbInt(vv); + v = (PyLongObject *)_PyLong_FromNbIndexOrNbInt(vv); if (v == NULL) return -1; do_decref = 1; @@ -700,7 +767,7 @@ PyLong_AsUnsignedLongMask(PyObject *op) return _PyLong_AsUnsignedLongMask(op); } - lo = _PyLong_FromNbInt(op); + lo = (PyLongObject *)_PyLong_FromNbIndexOrNbInt(op); if (lo == NULL) return (unsigned long)-1; @@ -1229,7 +1296,7 @@ PyLong_AsLongLong(PyObject *vv) v = (PyLongObject *)vv; } else { - v = _PyLong_FromNbInt(vv); + v = (PyLongObject *)_PyLong_FromNbIndexOrNbInt(vv); if (v == NULL) return -1; do_decref = 1; @@ -1344,7 +1411,7 @@ PyLong_AsUnsignedLongLongMask(PyObject *op) return _PyLong_AsUnsignedLongLongMask(op); } - lo = _PyLong_FromNbInt(op); + lo = (PyLongObject *)_PyLong_FromNbIndexOrNbInt(op); if (lo == NULL) return (unsigned long long)-1; @@ -1384,7 +1451,7 @@ PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow) v = (PyLongObject *)vv; } else { - v = _PyLong_FromNbInt(vv); + v = (PyLongObject *)_PyLong_FromNbIndexOrNbInt(vv); if (v == NULL) return -1; do_decref = 1; diff --git a/Python/getargs.c b/Python/getargs.c index c491169abe5..0e07555481c 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -650,7 +650,9 @@ converterr(const char *expected, PyObject *arg, char *msgbuf, size_t bufsize) #define CONV_UNICODE "(unicode conversion error)" /* Explicitly check for float arguments when integers are expected. - Return 1 for error, 0 if ok. */ + Return 1 for error, 0 if ok. + XXX Should be removed after the end of the deprecation period in + _PyLong_FromNbIndexOrNbInt. */ static int float_argument_error(PyObject *arg) { diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index f4f0017c292..0e7ce965b0f 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2602,6 +2602,8 @@ class bool_converter(CConverter): def parse_arg(self, argname, argnum): if self.format_unit == 'i': + # XXX PyFloat_Check can be removed after the end of the + # deprecation in _PyLong_FromNbIndexOrNbInt. return """ if (PyFloat_Check({argname})) {{{{ PyErr_SetString(PyExc_TypeError,