Title: int() and math.trunc don't accept objects that only define __index__
Type: enhancement Stage: needs patch
Components: Documentation Versions: Python 3.8, Python 3.7, Python 3.6
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: Eric Appelt, docs@python, mark.dickinson, ncoghlan, remi.lapeyre, xtreak
Priority: normal Keywords:

Created on 2018-03-10 08:57 by ncoghlan, last changed 2019-01-13 07:47 by ncoghlan.

Messages (4)
msg313515 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2018-03-10 08:57
(Note: I haven't categorised this yet, as I'm not sure how it *should* be categorised)

Back when the __index__/nb_index slot was added, the focus was on allowing 3rd party integer types to be used in places where potentially lossy conversion with __int__/nb_int *wasn't* permitted.

However, this has led to an anomaly where the lossless conversion method *isn't* tried implicitly for the potentially lossy int() and math.trunc() calls, but is tried automatically in other contexts:

>>> import math
>>> class MyInt:
...     def __index__(self):
...         return 42
>>> int(MyInt())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a number, not 'MyInt'
>>> math.trunc(MyInt())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type MyInt doesn't define __trunc__ method
>>> hex(MyInt())
>>> len("a" * MyInt())


Supporting int() requires also setting `__int__`:

>>> MyInt.__int__ = MyInt.__index__
>>> int(MyInt())

Supporting math.trunc() requires also setting `__trunc__`:

>>> MyInt.__trunc__ = MyInt.__index__
>>> math.trunc(MyInt())

(This anomaly was noticed by Eric Appelt while updating the int() docs to cover the fallback to trying __trunc__ when __int__ isn't defined:
msg313522 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2018-03-10 11:34
Marking this as a documentation enhancement request for now, but I think we should also consider changing the type creation behaviour in 3.8 to implicitly add __int__ and __trunc__ definitions when __index__ is defined, but they aren't.

That way, no behaviour will change for classes that explicitly define __int__ or __trunc__, but classes that only define __index__ without defining the other methods will behave more intuitively.
msg332819 - (view) Author: Rémi Lapeyre (remi.lapeyre) * Date: 2018-12-31 14:52
>I think we should also consider changing the type creation behaviour in 3.8

@ncoghlan is this what's being done in PyTypeReady?
msg333546 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2019-01-13 07:47
@Rémi Aye, filling out derived slots is one of the things PyType_Ready does.

This would need a discussion on python-dev before going ahead and doing it though, as the closest equivalent we have to this right now is the "negative" derivation, where overriding __eq__ without overriding __hash__ implicitly marks the derived class as unhashable (look for "type->tp_hash = PyObject_HashNotImplemented;").
Date User Action Args
2019-01-13 07:47:32ncoghlansetmessages: + msg333546
2018-12-31 14:52:24remi.lapeyresetnosy: + remi.lapeyre
messages: + msg332819
2018-09-22 17:00:28xtreaksetnosy: + xtreak
2018-03-10 11:34:44ncoghlansetassignee: docs@python
type: enhancement
components: + Documentation
versions: + Python 3.6, Python 3.7, Python 3.8
nosy: + docs@python

messages: + msg313522
stage: needs patch
2018-03-10 08:57:11ncoghlancreate