msg313515 - (view) |
Author: Alyssa Coghlan (ncoghlan) *  |
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 "", line 1, in 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 "", line 1, in TypeError: type MyInt doesn't define __trunc__ method >>> hex(MyInt()) '0x2a' >>> len("a" * MyInt()) 42 ``` Supporting int() requires also setting `__int__`: ``` >>> MyInt.__int__ = MyInt.__index__ >>> int(MyInt()) 42 ``` Supporting math.trunc() requires also setting `__trunc__`: ``` >>> MyInt.__trunc__ = MyInt.__index__ >>> math.trunc(MyInt()) 42 ``` (This anomaly was noticed by Eric Appelt while updating the int() docs to cover the fallback to trying __trunc__ when __int__ isn't defined: https://github.com/python/cpython/pull/6022#issuecomment-371695913) |
|
|
msg313522 - (view) |
Author: Alyssa Coghlan (ncoghlan) *  |
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: Alyssa Coghlan (ncoghlan) *  |
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;"). |
|
|
msg336044 - (view) |
Author: Serhiy Storchaka (serhiy.storchaka) *  |
Date: 2019-02-20 06:56 |
See also . |
|
|
msg336201 - (view) |
Author: Serhiy Storchaka (serhiy.storchaka) *  |
Date: 2019-02-21 10:45 |
Is not this a duplicate of ? |
|
|
msg336234 - (view) |
Author: Rémi Lapeyre (remi.lapeyre) * |
Date: 2019-02-21 16:10 |
Yes it is. Thanks for finding that @Serhiy. Since nobody objected to the change on the mailing list and people seem to agree in issue 20092: [R. David Murray] To summarize for anyone like me who didn't follow that issue: __index__ means the object can be losslessly converted to an int (is a true int), while __int__ may be an approximate conversion. Thus it makes sense for an object to have an __int__ but not __index__, but vice-versa does not make sense. I will post my patch tonight. |
|
|
msg340453 - (view) |
Author: Cheryl Sabella (cheryl.sabella) *  |
Date: 2019-04-17 22:43 |
Rémi, Are you still working on the patch for this? Thanks! |
|
|
msg341505 - (view) |
Author: Rémi Lapeyre (remi.lapeyre) * |
Date: 2019-05-06 14:11 |
Hi Cheryl, thanks for the ping. I wasn't sure my patch was correct but reading typeobject.c:add_operators(), it is actually more straight-forward than I thought. Serhiy Storchaka: This is indeed a duplicate of . I believe the solution proposed by Nick Coghlan is better than the one of Amitava Bhattacharyya, "adding a call to `nb_index` (if that slot exists) in `_PyLong_FromNbInt`" though. One thing to note regarding the proposed patch: the following stops to work and raises a RecursionError since __index__ == __int__: class MyInt(int): def __index__(self): return int(self) + 1 I changed test_int_subclass_with_index() as `int(self) + 1` is the same thing as `self + 1` for int subclasses. I don't think this sort of code should appear in the wild but if you think it is important not to break compatibility here, I think I could check for number subclasses before overriding __index__. |
|
|
msg341508 - (view) |
Author: Serhiy Storchaka (serhiy.storchaka) *  |
Date: 2019-05-06 14:29 |
Then let to continue the discussion on the older issue which has larger discussion. |
|
|