-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
fraction.Fraction does not implement __int__. #88713
Comments
While int, float, complex and Decimal implement __int__, Fraction does not. Thus, checking for typing.SupportsInt for fractions fails, although int(<fraction>) succeeds, because Fraction implements __trunc__. |
Seems like an equally reasonable solution would be to make class's with __trunc__ but not __int__ automatically generate a __int__ in terms of __trunc__ (similar to __str__ using __repr__ when the latter is defined but not the former). The inconsistency is in both methods existing, but having the equivalence implemented in int() rather than in the type (thereby making SupportsInt behave unexpectedly, even though it's 100% true that obj.__int__() would fail). |
FWIW, there's some history here: there's a good reason that fractions.Fraction didn't originally implement __int__. Back in the Bad Old Days, many Python functions that expected an integer would accept anything whose type implemented __int__ instead, and call __int__ to get the required integer. For example: Python 3.7.10 (default, Jun 1 2021, 23:43:35)
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from decimal import Decimal
>>> chr(Decimal("45.67"))
'-' Effectively, int was being used to mean two different things: (1) this value can be used as an integer, and (2) this value can be truncated to an integer. The solution was to introduce two new dedicated magic methods index and trunc for these two different meanings. See PEP-357 and PEP-3141 for some of the details. So adding int to fractions.Fraction would have made things like The behaviour above is still present (with a deprecation warning) in Python 3.9, and We may now finally be in a state where ill-advised uses of __int__ in internal functions have all been deprecated and removed, so that it's safe to re-add __int__ methods. But still, this seems mostly like an issue with the typing library. What is typing.SupportsInt intended to indicate? That an object can be used _as_ an integer, or that an object can be _truncated_ to an integer? |
I'm actually struggling to think of situations where typing.SupportsInt would be useful in its current form: if I'm writing a function that wants to do a duck-typed acceptance of integer-like things (for example because I want my function to work with NumPy integers as well as plain old Python ints) then I want an __index__ check rather than an __int__ check. If I'm writing a function that allows general numeric inputs, then I'm not sure why I'd be calling 'int' on those inputs. As another data point, complex supporting __int__ is a little bit of an oddity, since all that __int__ method does is raise a TypeError. @michael: are you in a position to share the use-case that motivated opening the issue? I'd be interested to see any concrete uses of typing.SupportsInt. Maybe typing.SupportsIndex (or typing.UsableAsInt, or ... --- naming things is hard) is what we actually need? On this particular issue: I'm not opposed to adding __int__ to fractions.Fraction purely for the sake of consistency, but it's not yet clear to me that it solves any real issue. |
Apologies: that already exists, of course. It was introduced in bpo-36972. |
This was fixed in 3.10: bpo-41974 |
The background is an implementation of __pow__ for a fixed-point decimal number: SupportsIntOrFloat = Union[SupportsInt, SupportsFloat]
def __pow__(self, other: SupportsIntOrFloat, mod: Any = None) -> Complex:
if isinstance(other, SupportsInt):
exp = int(other)
if exp == other:
... handle integer exponent
if isinstance(other, SupportsFloat):
# fractional exponent => fallback to float
return float(self) ** float(other)
return NotImplemented I came across SupportsInt and SupportsFloat, because they are used in typeshed as param types for int and float. |
Thanks, that's helpful. I guess what you _really_ want there is a duck-typed "tell me whether this value is integral and if so give me the corresponding Python int", but that's not currently easily available, so I suppose x == int(x) is the next-best thing. Possibly the "right" way from the point of view of PEP-3141 is to be testing x == math.trunc(x) instead and asking for typing.SupportsTrunc, but it seems to me that __trunc__ never did really take off the way it was intended to. tl;dr: I agree it would make sense to add __int__ to fractions.Fraction for 3.11. |
In ideal world we would use __int__ for (1), and __trunc__ for (2). But for some historical reasons __index__ was introduced for (1) and __int__ is only used in the int constructor, although it falls back to __trunc__. I am wondering whether one of __int__ or __trunc__ should be deprecated. |
I would not miss __trunc__. |
On other hand, there are classes which define __int__ but not __trunc__: UUID and IP4Address. So it makes sense to keep separate __int__ and __trunc__. |
Fraction.__int__ = Fraction.__trunc__ may not work because __trunc__() can return any object with __index__, while __int__ should return an exact int (not even an int subclass). |
I think there's no reason not to keep __trunc__ and math.trunc - they're natural counterparts to floor and ceil, and there's probably at least some code out there already using math.trunc. It's the involvement of __trunc__ in the int() builtin that I'd quite like to deprecate and eventually remove. I think at this point it complicates the object model for no particularly good reason. But this is getting off-topic for this issue; I'll open a new one. |
Thanks for the patch, Mark! ✨ 🍰 ✨ |
I'm going to note, this code is subtly confusing/wrong. Since In practice, you really want to be testing for |
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: