Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Doc/c-api/complex.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@ Complex Numbers as Python Objects

If *op* is not a Python complex number object but has a :meth:`__complex__`
method, this method will first be called to convert *op* to a Python complex
number object. Upon failure, this method returns ``-1.0`` as a real value.
number object. If ``__complex__()`` is not defined then it falls back to
:meth:`__float__`. If ``__float__()`` is not defined then it falls back
to :meth:`__index__`. Upon failure, this method returns ``-1.0`` as a real
value.

.. versionchanged:: 3.8
Use :meth:`__index__` if available.
4 changes: 4 additions & 0 deletions Doc/c-api/float.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ Floating Point Objects
Return a C :c:type:`double` representation of the contents of *pyfloat*. If
*pyfloat* is not a Python floating point object but has a :meth:`__float__`
method, this method will first be called to convert *pyfloat* into a float.
If ``__float__()`` is not defined then it falls back to :meth:`__index__`.
This method returns ``-1.0`` upon failure, so one should call
:c:func:`PyErr_Occurred` to check for errors.

.. versionchanged:: 3.8
Use :meth:`__index__` if available.


.. c:function:: double PyFloat_AS_DOUBLE(PyObject *pyfloat)

Expand Down
21 changes: 19 additions & 2 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ are always available. They are listed here in alphabetical order.
:class:`int` and :class:`float`. If both arguments are omitted, returns
``0j``.

For a general Python object ``x``, ``complex(x)`` delegates to
``x.__complex__()``. If ``__complex__()`` is not defined then it falls back
to :meth:`__float__`. If ``__float__()`` is not defined then it falls back
to :meth:`__index__`.

.. note::

When converting from a string, the string must not contain whitespace
Expand All @@ -330,6 +335,10 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.6
Grouping digits with underscores as in code literals is allowed.

.. versionchanged:: 3.8
Falls back to :meth:`__index__` if :meth:`__complex__` and
:meth:`__float__` are not defined.


.. function:: delattr(object, name)

Expand Down Expand Up @@ -584,7 +593,8 @@ are always available. They are listed here in alphabetical order.
float, an :exc:`OverflowError` will be raised.

For a general Python object ``x``, ``float(x)`` delegates to
``x.__float__()``.
``x.__float__()``. If ``__float__()`` is not defined then it falls back
to :meth:`__index__`.

If no argument is given, ``0.0`` is returned.

Expand All @@ -609,6 +619,9 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.7
*x* is now a positional-only parameter.

.. versionchanged:: 3.8
Falls back to :meth:`__index__` if :meth:`__float__` is not defined.


.. index::
single: __format__
Expand Down Expand Up @@ -780,7 +793,8 @@ are always available. They are listed here in alphabetical order.

Return an integer object constructed from a number or string *x*, or return
``0`` if no arguments are given. If *x* defines :meth:`__int__`,
``int(x)`` returns ``x.__int__()``. If *x* defines :meth:`__trunc__`,
``int(x)`` returns ``x.__int__()``. If *x* defines :meth:`__index__`,
it returns ``x.__index__()``. If *x* defines :meth:`__trunc__`,
Comment thread
serhiy-storchaka marked this conversation as resolved.
it returns ``x.__trunc__()``.
For floating point numbers, this truncates towards zero.

Expand Down Expand Up @@ -812,6 +826,9 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.7
*x* is now a positional-only parameter.

.. versionchanged:: 3.8
Falls back to :meth:`__index__` if :meth:`__int__` is not defined.


.. function:: isinstance(object, classinfo)

Expand Down
8 changes: 3 additions & 5 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2394,11 +2394,9 @@ left undefined.
functions). Presence of this method indicates that the numeric object is
an integer type. Must return an integer.

.. note::

In order to have a coherent integer type class, when :meth:`__index__` is
defined :meth:`__int__` should also be defined, and both should return
the same value.
If :meth:`__int__`, :meth:`__float__` and :meth:`__complex__` are not
defined then corresponding built-in functions :func:`int`, :func:`float`
and :func:`complex` fall back to :meth:`__index__`.


.. method:: object.__round__(self, [,ndigits])
Expand Down
11 changes: 10 additions & 1 deletion Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ Other Language Changes
compatible with the existing :meth:`float.as_integer_ratio` method.
(Contributed by Lisa Roach in :issue:`33073`.)

* Constructors of :class:`int`, :class:`float` and :class:`complex` will now
use the :meth:`~object.__index__` special method, if available and the
corresponding method :meth:`~object.__int__`, :meth:`~object.__float__`
or :meth:`~object.__complex__` is not available.
(Contributed by Serhiy Storchaka in :issue:`20092`.)

* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)

Expand Down Expand Up @@ -868,7 +874,10 @@ Build and C API Changes
``__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`.)
:c:func:`PyNumber_Long`, :c:func:`PyNumber_Float` and
:c:func:`PyFloat_AsDouble` also now use the ``__index__()`` method if
available.
(Contributed by Serhiy Storchaka in :issue:`36048` and :issue:`20092`.)

* Heap-allocated type objects will now increase their reference count
in :c:func:`PyObject_Init` (and its parallel macro ``PyObject_INIT``)
Expand Down
7 changes: 3 additions & 4 deletions Lib/test/test_cmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,11 @@ class NeitherComplexNorFloat(object):
pass
class NeitherComplexNorFloatOS:
pass
class MyInt(object):
class Index:
def __int__(self): return 2
def __index__(self): return 2
class MyIntOS:
class MyInt:
def __int__(self): return 2
def __index__(self): return 2

# other possible combinations of __float__ and __complex__
# that should work
Expand Down Expand Up @@ -255,6 +254,7 @@ def __float__(self):
self.assertEqual(f(FloatAndComplexOS()), f(cx_arg))
self.assertEqual(f(JustFloat()), f(flt_arg))
self.assertEqual(f(JustFloatOS()), f(flt_arg))
self.assertEqual(f(Index()), f(int(Index())))
# TypeError should be raised for classes not providing
# either __complex__ or __float__, even if they provide
# __int__ or __index__. An old-style class
Expand All @@ -263,7 +263,6 @@ def __float__(self):
self.assertRaises(TypeError, f, NeitherComplexNorFloat())
self.assertRaises(TypeError, f, MyInt())
self.assertRaises(Exception, f, NeitherComplexNorFloatOS())
self.assertRaises(Exception, f, MyIntOS())
# non-complex return value from __complex__ -> TypeError
for bad_complex in non_complexes:
self.assertRaises(TypeError, f, MyComplex(bad_complex))
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,24 @@ def __float__(self):
self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
self.assertRaises(TypeError, complex, float2(None))

class MyIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value

self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j)
self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j)
self.assertRaises(OverflowError, complex, MyIndex(2**2000))
self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000))

class MyInt:
def __int__(self):
return 42

self.assertRaises(TypeError, complex, MyInt())
self.assertRaises(TypeError, complex, 123, MyInt())

class complex0(complex):
"""Test usage of __complex__() when inheriting from 'complex'"""
def __complex__(self):
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ def __float__(self):
with self.assertWarns(DeprecationWarning):
self.assertIs(type(FloatSubclass(F())), FloatSubclass)

class MyIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value

self.assertEqual(float(MyIndex(42)), 42.0)
self.assertRaises(OverflowError, float, MyIndex(2**2000))

class MyInt:
def __int__(self):
return 42

self.assertRaises(TypeError, float, MyInt())

def test_keyword_args(self):
with self.assertRaisesRegex(TypeError, 'keyword argument'):
float(x='3.14')
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_getargs2.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,8 @@ def test_f(self):
with self.assertWarns(DeprecationWarning):
self.assertEqual(getargs_f(BadFloat2()), 4.25)
self.assertEqual(getargs_f(BadFloat3(7.5)), 7.5)
self.assertEqual(getargs_f(Index()), 99.0)
self.assertRaises(TypeError, getargs_f, Int())

for x in (FLT_MIN, -FLT_MIN, FLT_MAX, -FLT_MAX, INF, -INF):
self.assertEqual(getargs_f(x), x)
Expand Down Expand Up @@ -489,6 +491,8 @@ def test_d(self):
with self.assertWarns(DeprecationWarning):
self.assertEqual(getargs_d(BadFloat2()), 4.25)
self.assertEqual(getargs_d(BadFloat3(7.5)), 7.5)
self.assertEqual(getargs_d(Index()), 99.0)
self.assertRaises(TypeError, getargs_d, Int())

for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF):
self.assertEqual(getargs_d(x), x)
Expand All @@ -511,6 +515,8 @@ def test_D(self):
with self.assertWarns(DeprecationWarning):
self.assertEqual(getargs_D(BadComplex2()), 4.25+0.5j)
self.assertEqual(getargs_D(BadComplex3(7.5+0.25j)), 7.5+0.25j)
self.assertEqual(getargs_D(Index()), 99.0+0j)
self.assertRaises(TypeError, getargs_D, Int())

for x in (DBL_MIN, -DBL_MIN, DBL_MAX, -DBL_MAX, INF, -INF):
c = complex(x, 1.0)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_int_subclass_with_index(self):
# subclasses. See issue #17576.
class MyInt(int):
def __index__(self):
return int(self) + 1
return int(str(self)) + 1

my_int = MyInt(7)
direct_index = my_int.__index__()
Expand Down
66 changes: 60 additions & 6 deletions Lib/test/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,23 @@ def __trunc__(self):
int(ExceptionalTrunc())

for trunc_result_base in (object, Classic):
class Integral(trunc_result_base):
def __int__(self):
class Index(trunc_result_base):
def __index__(self):
return 42

class TruncReturnsNonInt(base):
def __trunc__(self):
return Integral()
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(TruncReturnsNonInt()), 42)
return Index()
self.assertEqual(int(TruncReturnsNonInt()), 42)

class Intable(trunc_result_base):
def __int__(self):
return 42

class TruncReturnsNonIndex(base):
def __trunc__(self):
return Intable()
self.assertEqual(int(TruncReturnsNonInt()), 42)

class NonIntegral(trunc_result_base):
def __trunc__(self):
Expand Down Expand Up @@ -418,6 +426,21 @@ def __trunc__(self):
with self.assertRaises(TypeError):
int(TruncReturnsBadInt())

def test_int_subclass_with_index(self):
class MyIndex(int):
def __index__(self):
return 42

class BadIndex(int):
def __index__(self):
return 42.0

my_int = MyIndex(7)
self.assertEqual(my_int, 7)
self.assertEqual(int(my_int), 7)

self.assertEqual(int(BadIndex()), 0)

def test_int_subclass_with_int(self):
class MyInt(int):
def __int__(self):
Expand All @@ -431,9 +454,19 @@ def __int__(self):
self.assertEqual(my_int, 7)
self.assertEqual(int(my_int), 42)

self.assertRaises(TypeError, int, BadInt())
my_int = BadInt(7)
self.assertEqual(my_int, 7)
self.assertRaises(TypeError, int, my_int)

def test_int_returns_int_subclass(self):
class BadIndex:
def __index__(self):
return True

class BadIndex2(int):
def __index__(self):
return True

class BadInt:
def __int__(self):
return True
Expand All @@ -442,6 +475,10 @@ class BadInt2(int):
def __int__(self):
return True

class TruncReturnsBadIndex:
def __trunc__(self):
return BadIndex()

class TruncReturnsBadInt:
def __trunc__(self):
return BadInt()
Expand All @@ -450,6 +487,17 @@ class TruncReturnsIntSubclass:
def __trunc__(self):
return True

bad_int = BadIndex()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
self.assertEqual(n, 1)
self.assertIs(type(n), int)

bad_int = BadIndex2()
n = int(bad_int)
self.assertEqual(n, 0)
self.assertIs(type(n), int)

bad_int = BadInt()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
Expand All @@ -462,6 +510,12 @@ def __trunc__(self):
self.assertEqual(n, 1)
self.assertIs(type(n), int)

bad_int = TruncReturnsBadIndex()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
self.assertEqual(n, 1)
self.assertIs(type(n), int)

bad_int = TruncReturnsBadInt()
with self.assertWarns(DeprecationWarning):
n = int(bad_int)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Constructors of :class:`int`, :class:`float` and :class:`complex` will now
use the :meth:`~object.__index__` special method, if available and the
corresponding method :meth:`~object.__int__`, :meth:`~object.__float__`
or :meth:`~object.__complex__` is not available.
19 changes: 19 additions & 0 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,13 @@ PyNumber_Long(PyObject *o)
}
return result;
}
if (m && m->nb_index) {
result = _PyLong_FromNbIndexOrNbInt(o);
if (result != NULL && !PyLong_CheckExact(result)) {
Py_SETREF(result, _PyLong_Copy((PyLongObject *)result));
}
return result;
}
trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__);
if (trunc_func) {
result = _PyObject_CallNoArg(trunc_func);
Expand Down Expand Up @@ -1480,6 +1487,18 @@ PyNumber_Float(PyObject *o)
Py_DECREF(res);
return PyFloat_FromDouble(val);
}
if (m && m->nb_index) {
PyObject *res = PyNumber_Index(o);
if (!res) {
return NULL;
}
double val = PyLong_AsDouble(res);
Comment thread
serhiy-storchaka marked this conversation as resolved.
Py_DECREF(res);
if (val == -1.0 && PyErr_Occurred()) {
return NULL;
}
return PyFloat_FromDouble(val);
}
if (PyFloat_Check(o)) { /* A float subclass with nb_float == NULL */
return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o));
}
Expand Down
Loading