summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2019-02-25 17:57:58 +0200
committerGitHub <noreply@github.com>2019-02-25 17:57:58 +0200
commit6a44f6eef3d0958d88882347190b3e2d1222c2e9 (patch)
treebb6474b1e094a672837c3333a081fa4d5d7638f2
parentd90a141bb947b68601f8d1f37bc98f7b524f0e01 (diff)
downloadcpython-git-6a44f6eef3d0958d88882347190b3e2d1222c2e9.tar.gz
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.
-rw-r--r--Doc/c-api/long.rst66
-rw-r--r--Doc/c-api/number.rst3
-rw-r--r--Doc/whatsnew/3.8.rst20
-rw-r--r--Include/longobject.h12
-rw-r--r--Lib/ctypes/test/test_numbers.py8
-rw-r--r--Lib/datetime.py37
-rw-r--r--Lib/test/datetimetester.py7
-rw-r--r--Lib/test/test_array.py2
-rw-r--r--Lib/test/test_getargs2.py109
-rw-r--r--Lib/test/test_int.py3
-rw-r--r--Lib/test/test_zlib.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2019-02-20-08-51-04.bpo-36048.I3LJt9.rst4
-rw-r--r--Modules/arraymodule.c2
-rw-r--r--Modules/zlibmodule.c4
-rw-r--r--Objects/abstract.c11
-rw-r--r--Objects/longobject.c85
-rw-r--r--Python/getargs.c4
-rwxr-xr-xTools/clinic/clinic.py2
18 files changed, 326 insertions, 55 deletions
diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst
index 5b1f386fb7..8093f4b627 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 296b21c132..9fb220b192 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 68a4457106..154fd66630 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 82c06c92a6..b696f544b9 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 09eef90f74..c6d843b2cd 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 89c32c0b0a..85bfa48e05 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 958b33675c..715f0ea6b4 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 7f402f8644..2399980d13 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 d9bcb1d75b..07e2d15137 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 c048b712da..307ca36bb4 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 bf5d64ceb2..f828b4c737 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 0000000000..d032e84341
--- /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 f5f461b756..a5ba27cb36 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 00bbe21fc0..5778dbb715 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 0565ba34f5..68d06edfa6 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 d7b01cebac..1e3445c64a 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 c491169abe..0e07555481 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 f4f0017c29..0e7ce965b0 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,