-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
type() constructor should bind __int__ to __index__ when __index__ is defined and __int__ is not #64291
Comments
In order to create a coherent integer type class both __int__ and __index__ must be defined or the resulting instances will behave inconsistently in different places. For example, if __index__ is not defined then the class cannot be used in slices, and if __int__ is not defined then int(integer_type) will fail. At this point the programmer must remember to define both, but since they should return the same value we can have the type constructor automatically assign __int__ to __index__ when the latter is defined and the former is not. This would be similar to how we currently treat __ne__ when __eq__ is defined. |
__ne__ is not "bound" to __eq__. The comparison code simply falls back onto __eq__ if __ne__ is not defined. |
True. I meant similar in that Python will use what's available to fill in the blank: class has __eq__ but user tried != ? use __eq__ and invert result class has __index__ but user tried int() ? use __index__ as-is |
It is not clear to me that that would be correct, though. Isn't the whole point of __index__ that some types can act as indicies even though they are *not* integers? Shouldn't it be up to the type to decide if converting them to int is a sensible thing to do? Maybe that's being silly/pendantic, though. On the gripping hand, it feels like this is another case of what you have pointed out elsewhere, that it is not clear that we actually have a consistent API here... |
In bpo-19995, in msg206339, Guido exhorted:
The de facto API that is forming is that if an actual int is needed from an actual integer type (not float, not complex, not etc.), then __index__ is used. If __index__ is not defined by some numeric type then it will not be considered a true int in certain key places in Python, such as as indices, arguments to hex(), etc. Making the change suggested in the title would help solidify the API. |
Ah, I see. A link to that issue would have been helpful :). 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. Is someone updating the docs to reflect this, or should that be spun off as a separate issue as well? |
I have the following as part of the patch for that issue: diff -r b668c409c10a Doc/reference/datamodel.rst
--- a/Doc/reference/datamodel.rst Sat Dec 28 20:37:58 2013 +0100
+++ b/Doc/reference/datamodel.rst Sun Dec 29 06:55:11 2013 -0800
@@ -2073,23 +2073,31 @@ left undefined.
builtin: float
builtin: round
Called to implement the built-in functions :func:`complex`,
:func:`int`, :func:`float` and :func:`round`. Should return a value
of the appropriate type.
.. method:: object.__index__(self)
- Called to implement :func:`operator.index`. Also called whenever Python needs
- an integer object (such as in slicing, or in the built-in :func:`bin`,
- :func:`hex` and :func:`oct` functions). Must return an integer.
+ Called to implement :func:`operator.index`, and whenever Python needs to
+ losslessly convert the numeric object to an integer object (such as in
+ slicing, or in the built-in :func:`bin`, :func:`hex` and :func:`oct`
+ functions). Presence of this method indicates that the numeric object is
+ an integer type. Must return an integer.
+
+ .. note::
+
+ When :meth:`__index__` is defined, :meth:`__int__` should also be defined,
+ and both shuld return the same value, in order to have a coherent integer
+ type class. If for some reason that patch doesn't make it into 3.4 I'll split the doc change off to its own issue, unless you think it should be split off anyway? |
Nah, splitting it doesn't seem worth it unless you think the patch won't make it in. (Not that I looked at it earlier, but he patch on the issue doesn't look like what you just posted here...and here there's a typo: shuld). |
I updated it as I liked your wording better. :) Doing more testing to see if anything else needs fixing before I make the next patch for the tracker on that issue. |
I started working on this issue as part of the Bloomberg Python Sprint (https://etherpad.mozilla.org/LjZPQ55oZs). Ethan, can you confirm if the attached test case summarizes the issue correctly? I tried to hook _PyLong_FromNbInt to check nb_index but didn't make progress there. Will need to load in a debugger to see what's going on. |
Yes, that test should pass when this issue is resolved. I can't tell from your comment what you are trying to do to resolve it, but the course of action I had in mind was to have the |
Hi Ethan, I tried adding a call to |
Thank you for your efforts, Amitava. Please also sign a Contributor's License Agreement so we can actually use your code. :)
|
There will be a corporate agreement from Bloomberg sometime soon (and that will be required in this case, not an individual agreement). |
I did fill in the contributor agreement form and e-signed it, maybe it takes some time for my profile to be updated? But I guess I need to wait for the corporate agreement. :) |
Pingback from bpo-33002, which is caused by the fact that parts of the CPython code base that use PyNumber_Index for type conversion still pre-check for valid types with PyNumber_Check, meaning that a type with __index__ and not __int__ gets erroneously rejected by the pre-check. |
See also the discussion on the duplicated bpo-33039. Few months ago I wrote the PR that makes constructors of int, float and complex to fall back to __index__ if corresponding special methods __int__, __float__ and __complex__ are not defined. I did not exposed it to public because binding __int__ to __index__ looks better to me. But perhaps some tests from that PR can be used in an alternate PR. |
I like the delegation of |
I ended up in this issue after I learnt the following from the Python Library Reference Manual. ---- For a general Python object x, float(x) delegates to x.__float__(). If __float__() is not defined then it falls back to __index__(). The discussion on __int__() and __index__() was very interesting but I still didn't get the answer I wanted. If __int__() is assumed to be a possibly approximate conversion and it's possible that __int__() may exist while __index__() doesn't, shouldn't __int__() be used as a fall back before __index__()? The downside would be that the resulting float may not be "very close" to the original object because __int__() is only an approximation while __index__() guarantees exact, but loss of precision is acceptable during type conversion, isn't it? (i.e. int(3.14) -> 3). Perhaps it's not acceptable if the conversion is a widening conversion and that's why __int__() is skipped? |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: