]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-36048: Use __index__() instead of __int__() for implicit conversion if available...
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 25 Feb 2019 15:57:58 +0000 (17:57 +0200)
committerGitHub <noreply@github.com>
Mon, 25 Feb 2019 15:57:58 +0000 (17:57 +0200)
Deprecate using the __int__() method in implicit conversions of Python
numbers to C integers.

18 files changed:
Doc/c-api/long.rst
Doc/c-api/number.rst
Doc/whatsnew/3.8.rst
Include/longobject.h
Lib/ctypes/test/test_numbers.py
Lib/datetime.py
Lib/test/datetimetester.py
Lib/test/test_array.py
Lib/test/test_getargs2.py
Lib/test/test_int.py
Lib/test/test_zlib.py
Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst [new file with mode: 0644]
Modules/arraymodule.c
Modules/zlibmodule.c
Objects/abstract.c
Objects/longobject.c
Python/getargs.c
Tools/clinic/clinic.py

index 5b1f386fb7e58fa5e9a2cab0adbe0eab816ede68..8093f4b627a2b9b1a2ffa00b8fc3f5d927654cf1 100644 (file)
@@ -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)
 
index 296b21c132b79a48f1ca8b37d4bd412653d091cc..9fb220b192b9a63c9877ab2e2395a1aa9ee9755a 100644 (file)
@@ -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)
 
index 68a4457106bb34e42108505ad130a2ff9386ad12..154fd66630ead9b2bb8fc58b19052f80f28d685e 100644 (file)
@@ -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
 ========================
 
index 82c06c92a6389946c4a924d6f960fd78a689f72d..b696f544b9c19245af0cc31034300daca39705cf 100644 (file)
@@ -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. */
index 09eef90f748db3dfd89b3c0bbdabc08930e900e1..c6d843b2cd987f7c1aba552bfa3e8d5fbc41f8a0 100644 (file)
@@ -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):
index 89c32c0b0a630123c479a134f075b1bca3485567..85bfa48e05dea4bc9a145efe632c8f0d38e67b33 100644 (file)
@@ -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)
index 958b33675c37b32c58440033a944d2254bf6f355..715f0ea6b40d239379ef5b522c1c4c3ec4bf6df4 100644 (file)
@@ -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):
index 7f402f8644710303a865cd8c4ef8524de234dd93..2399980d134c8bbdf94f4bee12540edae9dd36e4 100644 (file)
@@ -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):
index d9bcb1d75bd029f909d937124a96c40719da6b39..07e2d151379182f81339c97cf3fb5589d16d9aa7 100644 (file)
@@ -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())
index c048b712da83ceb65298f2fa85ab8c0424c5ac74..307ca36bb4faf44110bacee6b6f6a0279b03b18a 100644 (file)
@@ -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):
index bf5d64ceb2988fffeec20ec830606a8761e241da..f828b4c737a5fcf7f14633076a601ab0b533e382 100644 (file)
@@ -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 (file)
index 0000000..d032e84
--- /dev/null
@@ -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.
index f5f461b756484b12ab8bc621f2b1189387687d99..a5ba27cb36e21800c6aef2b7050fdba7b94daa3a 100644 (file)
@@ -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
index 00bbe21fc0bd827668d9f8cb41de9cd2bd1ae52d..5778dbb715f92c4acd43bd80764beba394ed3c85 100644 (file)
@@ -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;
     }
index 0565ba34f5b8234f4d6249be655de990f775005d..68d06edfa6008059fd5a86b7d71a19c09be16731 100644 (file)
@@ -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));
         }
index d7b01cebac850c9b2bdeed078754782d0b143073..1e3445c64a6a2bd88c1291a362e07f465deacc81 100644 (file)
@@ -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;
index c491169abe5793d2eaec85f3094be0181e6daf6a..0e07555481c6c23a936a890f0af98d2bbdcd1f3d 100644 (file)
@@ -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)
 {
index f4f0017c292449765410b37c80901357b060cfeb..0e7ce965b0f1e3ae790352c9fb270e2280bb4354 100755 (executable)
@@ -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,