-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add more return types to the numbers module
#11353
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
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
That looks like the kind of diff I'd expect, given that type checkers don't support these ABCs at all. I think it's a net win:
|
JelleZijlstra
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is a good step forward.
However, we can probably do a bit better by using protocols that support a few more common unary dunder methods that exist on common number classes: __abs__ and others.
Also, I don't think we can realistically annotate the parameters to binary dunder methods on these classes. You'd think that say Integral.__add__ should accept any other Integral, but that's impossible to implement in a concrete class (how would it know to add itself to any other Integral that might exist somewhere?) and inherently an LSP violation, because Integral is a subclass of Complex, and Complex.__add__ should allow any complex numbers. So I endorse keeping parameter annotations out of this PR.
Okay, I've had a go! Let's see what primer says... |
This comment has been minimized.
This comment has been minimized.
|
That last commit made the typing a lot more complex, didn't change the primer output at all, and broke our test cases for |
This reverts commit 687709c.
Everything I tried locally seemed to break either our test cases for |
|
Diff from mypy_primer, showing the effect of this PR on open source code: steam.py (https://github.com/Gobot1234/steam.py)
- steam/ext/tf2/currency.py:100: note: Superclass:
- steam/ext/tf2/currency.py:100: note: @overload
- steam/ext/tf2/currency.py:100: note: def __sub__(a, int | Fraction, /) -> Fraction
- steam/ext/tf2/currency.py:100: note: @overload
- steam/ext/tf2/currency.py:100: note: def __sub__(a, float, /) -> float
- steam/ext/tf2/currency.py:100: note: @overload
- steam/ext/tf2/currency.py:100: note: def __sub__(a, complex, /) -> complex
- steam/ext/tf2/currency.py:100: note: Subclass:
- steam/ext/tf2/currency.py:100: note: def __sub__(self, int | str | float | Fraction | Decimal, /) -> Metal
pytest (https://github.com/pytest-dev/pytest)
+ src/_pytest/python_api.py:400: error: Unused "type: ignore" comment [unused-ignore]
+ src/_pytest/python_api.py:444: error: Unused "type: ignore" comment [unused-ignore]
+ src/_pytest/python_api.py:445: error: Unused "type: ignore" comment [unused-ignore]
+ src/_pytest/python_api.py:453: error: Unused "type: ignore" comment [unused-ignore]
+ src/_pytest/python_api.py:457: error: Argument 1 to "abs" has incompatible type "SupportsComplex"; expected "SupportsAbs[Never]" [arg-type]
ibis (https://github.com/ibis-project/ibis)
+ ibis/util.py:168: error: Unsupported left operand type for < (Never) [operator]
+ ibis/util.py:168: error: Argument 1 to "abs" has incompatible type "SupportsComplex"; expected "SupportsAbs[Never]" [arg-type]
- ibis/common/temporal.py:184: error: Incompatible return value type (got "int", expected "timedelta") [return-value]
- ibis/common/temporal.py:184: error: Argument 1 to "int" has incompatible type "timedelta | Real"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type]
+ ibis/common/temporal.py:184: error: No overload variant of "int" matches argument type "timedelta | Real" [call-overload]
+ ibis/common/temporal.py:184: note: Possible overload variants:
+ ibis/common/temporal.py:184: note: def __new__(cls, str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = ..., /) -> int
+ ibis/common/temporal.py:184: note: def __new__(cls, str | bytes | bytearray, /, base: SupportsIndex) -> int
|
This PR represents a compromise between what the
numbersmodule was supposed to be, and the reality of where we are today with Python's typing system.This module is supposed to provide a framework of ABCs that would allow for third-party implementations of Complex, Rational, Real and Integral numbers. As such, the annotations we should be giving to the
Realclass should be something like this:Unfortunately, that doesn't work! The reason is that
fractions.Fractioninherits fromnumbers.Real, and it overrides these abstract methods to provide concrete implementations. The concrete implementations return concrete types such asint, which type checkers don't recognise as subtypes ofReal(because thenumbersmodule has never been supported by static type checkers, and never will be):typeshed/stdlib/fractions.pyi
Lines 105 to 106 in 547cbc7
As such, for every method I tried adding types to according to the above strategy, mypy emitted an error warning me that
fractions.Fractionincompatibly overrodenumbers.Real. (There were also similar errors for anumbers.Realsubclass in ourPillowstubs.)Instead of returning
Complexfrom methods that really should be returningComplex, therefore, this PR annotates those methods as returningSupportsComplex.SupportsComplexworks pretty well here:numbers.Complex, so it's not incorrectAnynumbers.Complexfrom these methods, which is what the module was designed for anyway.In a similar way, I've annotated methods that should be returning
numbers.Integralas returningSupportsIndex, and I've annotated methods that should be returningnumbers.Realas returningSupportsFloat.I haven't attempted to add any new argument annotations as part of this PR. The return annotations were hard enough.