From a7a17299d221f3943abf95b88fdfb56e7d53a328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 6 May 2019 15:55:38 +0200 Subject: [PATCH 1/5] Test that __int__ defaults to __index__ --- Lib/test/test_index.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_index.py b/Lib/test/test_index.py index a2ac32132e2343..9a29bfd307ef67 100644 --- a/Lib/test/test_index.py +++ b/Lib/test/test_index.py @@ -89,6 +89,13 @@ def __index__(self): n = operator.index(bad_int) self.assertEqual(n, 0) + def test_int_defaults_to_index(self): + class Test: + def __index__(self): + return 4 + + self.assertEqual(int(Test()), 4) + class SeqTestCase: # This test case isn't run directly. It just defines common tests From 9ba4d39a1b499318c52f4c9575a6edc655d363fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Mon, 6 May 2019 15:58:13 +0200 Subject: [PATCH 2/5] Make __int__ defaults to __index__ --- Doc/reference/datamodel.rst | 6 ++--- Lib/test/test_index.py | 2 +- .../2019-05-06-16-00-50.bpo-33039.5YKYPc.rst | 2 ++ Objects/typeobject.c | 22 +++++++++++++++++-- 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-06-16-00-50.bpo-33039.5YKYPc.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 9fc9f3a3848a4e..5f7aa88ea31110 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2389,10 +2389,8 @@ left undefined. .. 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:`__index__` is defined and not :meth:`__int__`, :meth:`__int__` + automatically defaults to :meth:`__index__`. .. method:: object.__round__(self, [,ndigits]) object.__trunc__(self) diff --git a/Lib/test/test_index.py b/Lib/test/test_index.py index 9a29bfd307ef67..72a4a96a881a22 100644 --- a/Lib/test/test_index.py +++ b/Lib/test/test_index.py @@ -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 self + 1 my_int = MyInt(7) direct_index = my_int.__index__() diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-06-16-00-50.bpo-33039.5YKYPc.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-06-16-00-50.bpo-33039.5YKYPc.rst new file mode 100644 index 00000000000000..79d0742afbbb43 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-06-16-00-50.bpo-33039.5YKYPc.rst @@ -0,0 +1,2 @@ +:meth:`__int__` defaults to :meth:`__index__` when only the latter is +defined. Patch contributed by Rémi Lapeyre. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 37df4d23e4c192..048d83bfdce72b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -71,6 +71,8 @@ _Py_IDENTIFIER(__new__); _Py_IDENTIFIER(__set_name__); _Py_IDENTIFIER(__setitem__); _Py_IDENTIFIER(builtins); +_Py_IDENTIFIER(__index__); +_Py_IDENTIFIER(__int__); static PyObject * slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds); @@ -5393,8 +5395,9 @@ PyType_Ready(PyTypeObject *type) } } - /* Hack for tp_hash and __hash__. - If after all that, tp_hash is still NULL, and __hash__ is not in + /* Set default methods */ + + /* If after all that, tp_hash is still NULL, and __hash__ is not in tp_dict, set tp_hash to PyObject_HashNotImplemented and tp_dict['__hash__'] equal to None. This signals that __hash__ is not inherited. @@ -5410,6 +5413,21 @@ PyType_Ready(PyTypeObject *type) } } + /* If __index__ is defined but not __int__, make it default to __index__. + Don't touch __float__ and __complex__ as there could be some loss of + precision. + */ + PyObject* index = _PyDict_GetItemIdWithError(type->tp_dict, &PyId___index__); + if (PyErr_Occurred()) { + goto error; + } + if (index != NULL && _PyDict_GetItemIdWithError(type->tp_dict, &PyId___int__) == NULL) { + if (PyErr_Occurred() || + _PyDict_SetItemId(type->tp_dict, &PyId___int__, index) < 0) { + goto error; + } + } + /* Some more special stuff */ base = type->tp_base; if (base != NULL) { From bf39c88cd3fa6043b33fca3edd30189a69d4632e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Thu, 16 May 2019 17:08:17 +0200 Subject: [PATCH 3/5] Check PyErr_Occurred only when the result of _PyDict_GetItemIdWithError is null --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 048d83bfdce72b..06cfdf81450c59 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5418,7 +5418,7 @@ PyType_Ready(PyTypeObject *type) precision. */ PyObject* index = _PyDict_GetItemIdWithError(type->tp_dict, &PyId___index__); - if (PyErr_Occurred()) { + if (index == NULL && PyErr_Occurred()) { goto error; } if (index != NULL && _PyDict_GetItemIdWithError(type->tp_dict, &PyId___int__) == NULL) { From d9a8925d8966944efc5691d971484683ef4b8bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 17 May 2019 11:20:02 +0200 Subject: [PATCH 4/5] Make tp_int slot default to tp_int --- Objects/typeobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 06cfdf81450c59..15ea9dd2288e33 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5426,6 +5426,7 @@ PyType_Ready(PyTypeObject *type) _PyDict_SetItemId(type->tp_dict, &PyId___int__, index) < 0) { goto error; } + type->tp_as_number->nb_int = type->tp_as_number->nb_index; } /* Some more special stuff */ From 44ed7c5cb99f898eba79d355ca631b2f93d6666a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Sun, 19 May 2019 19:15:59 +0200 Subject: [PATCH 5/5] Use only slots --- Objects/typeobject.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 15ea9dd2288e33..ece9a43756f6fa 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -71,8 +71,6 @@ _Py_IDENTIFIER(__new__); _Py_IDENTIFIER(__set_name__); _Py_IDENTIFIER(__setitem__); _Py_IDENTIFIER(builtins); -_Py_IDENTIFIER(__index__); -_Py_IDENTIFIER(__int__); static PyObject * slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds); @@ -5413,22 +5411,6 @@ PyType_Ready(PyTypeObject *type) } } - /* If __index__ is defined but not __int__, make it default to __index__. - Don't touch __float__ and __complex__ as there could be some loss of - precision. - */ - PyObject* index = _PyDict_GetItemIdWithError(type->tp_dict, &PyId___index__); - if (index == NULL && PyErr_Occurred()) { - goto error; - } - if (index != NULL && _PyDict_GetItemIdWithError(type->tp_dict, &PyId___int__) == NULL) { - if (PyErr_Occurred() || - _PyDict_SetItemId(type->tp_dict, &PyId___int__, index) < 0) { - goto error; - } - type->tp_as_number->nb_int = type->tp_as_number->nb_index; - } - /* Some more special stuff */ base = type->tp_base; if (base != NULL) { @@ -5454,6 +5436,14 @@ PyType_Ready(PyTypeObject *type) goto error; } + /* If __index__ is defined but not __int__, make it default to __index__. + Don't touch __float__ and __complex__ as there could be some loss of + precision. + */ + if (type->tp_as_number != NULL && type->tp_as_number->nb_int == NULL) { + type->tp_as_number->nb_int = type->tp_as_number->nb_index; + } + /* All done -- set the ready flag */ type->tp_flags = (type->tp_flags & ~Py_TPFLAGS_READYING) | Py_TPFLAGS_READY;