Comments (22)
I don't really have anything to add to this conversation, except that I believe that historically when we wrote and accepted PEP 484, I personally believed that we were specifying that int
was a subtype of float
(and float
a subtype of complex
). Until today I wasn't aware that the spec actually says that e.g. the type float
should be interpreted, in certain contexts, as float | int
. This is neither here nor there, it's just an admission of my flawed understanding of the subtleties here at the time.
from typing.
Why allow passing 42 to an API which requires a
float
?
Because Python users have been doing that for the last 30 years.
from typing.
It could be also worth calling out that even though consistent subtyping/compatibility does allow
int
wherefloat
is expected, it is in fact unsound:def f(x: float) -> bool: return x.is_integer() f(42) # type checks, but fails at runtime
This passes for me, BTW. (Apparently this was a CPython bug and it was fixed in 3.12.)
The same applies to Eric's example using f.hex()
-- when that was added to float
it should also have been added to int
. Same for other examples you may find of methods that are supported by float
but not by int
.
Could you give an example which stops working if 42 is statically modelled as just
int
with no subtyping/consistent subtyping connection tofloat
?
This sounds like a trick question. I'd say that in 3.12 the above example works at runtime, but it seems you would like it to fail the static check? Why? Because it fails at runtime in 3.11?
from typing.
Perhaps it is orthogonal to the core concern of this issue, but if we amend this paragraph of the spec, I think it would also be better to use clearer terms for the relation that this special case applies to (e.g. "consistent with"), rather than discussing only argument annotations, as the current text does, which leaves underspecified whether the described behavior is also supposed to apply to non-argument annotations (e.g. x: int = 1; y: float = x
).
from typing.
It could be also worth calling out that even though consistent subtyping/compatibility does allow int
where float
is expected, it is in fact unsound:
def f(x: float) -> bool:
return x.is_integer()
f(42) # type checks, but fails at runtime
from typing.
It could be also worth calling out that even though consistent subtyping/compatibility does allow
int
wherefloat
is expected, it is in fact unsound:def f(x: float) -> bool: return x.is_integer() f(42) # type checks, but fails at runtime
I think that's already touched on where the spec says:
Python’s numeric types
complex
,float
andint
are not subtypes of each other
But perhaps it could be more explicitly stated, yes
from typing.
They are not subtypes, but they are part of the consistency (or rather the consistent subtyping) relation, which is what is normally used by type checkers for checking function calls. Am I interpreting the spec wording in the wrong way?
from typing.
I believe the int/float/complex
special-casing that type checkers perform is unique in that the special-casing only applies to parameter annotations; they are not seen by type checkers as subtypes or consistent subtypes in contexts outside of parameter annotations. I understood the spec to be referring to this unique aspect of the way these types are understood by type checkers when it said that they "are not subtypes of each other". But you're right it could be clearer; I'm not confident that my interpretation is correct.
from typing.
Note that regardless of the terminology, this feature is unsound since it is not type safe generally to pass an int
where a float
is expected (as my admittedly artificial) snippet above demonstrates.
from typing.
A discussion on the merits and demerits of the current behaviour of type checkers in this regard is outside of the scope of this issue.
from typing.
The typing spec is not meant to be a tutorial or reference for users of the type system. It's a specification that clarifies how type checkers should behave. As such, I think it's unnecessary (and arguably inappropriate) for it to discuss PEP 3141, since it has no bearing on the type system.
This topic could fit within the Typing Reference section of the docs. Contributions are welcome.
even though consistent subtyping/compatibility does allow int where float is expected...
As @AlexWaygood said above, that statement is subtly incorrect. The type int
is not a subtype of float
in the Python type system; the spec is clear that this is the case. Instead, the spec indicates that when you use float
within a type expression, a type checker should implicitly interpret that expression as though the user had specified float | int
. This is an important distinction.
this feature is unsound
That's not necessarily true. It depends on the type checker implementation. If a type checker assumes that int
is a subtype of float
(as mypy does), then it is unsound. If a type checker assumes that any annotation float
implies float | int
, then it's not unsound. There is a proposal in the mypy issue tracker to change mypy to do the latter, but it hasn't been implemented.
I attempted to implement this suggestion in pyright but needed to back out part of it for compatibility reasons. See this issue for details. The problem is that there's currently no way in the type system to provide an annotation that means "only float
, not float | int
", so methods like int.__truediv__
cannot be annotated in a way that allows a type checker to implement soundness checks without generating a bunch of false positives.
Here's an illustrative code sample that generates a runtime error. Pyright detects this error but mypy does not currently.
Code sample in pyright playground
def func1(f: float):
if not isinstance(f, float):
f.hex() # Runtime error
func1(1)
from typing.
Thanks for an extended reply, Eric.
The type int is not a subtype of float in the Python type system; the spec is clear that this is the case.
I think it would be useful if the spec clarified that int
it is neither a subtype, nor is consistent with float
. In my mind, saying that it isn't a subtype does not immediately eliminate it from the other relation.
Instead, the spec indicates that when you use float within a type expression, a type checker should implicitly interpret that expression as though the user had specified float | int.
I agree that this interpretation is sound, but I also find it confusing to treat float
(and complex
) as effectively a macro/syntax sugar.
Do you think we could re-evaluate this as part of the work in the spec? For example, we could explore an alternative design where literals have flexible types, so 42 would be {int, float, complex}
instead of just int
, and 42.0
-- {float, complex}
.
from typing.
Looking at
def func1(f: float):
if not isinstance(f, float):
f.hex() # Runtime error
func1(1)
as a user, my first reaction would be to conclude that the body of the if
is unreachable. This is true for any class, so it is natural to extend that to float
.
from typing.
Do you think we could re-evaluate this as part of the work in the spec?
The spec is a living document, and the community welcomes new proposals. If this is a topic that's important to you and you'd like to champion a modification, feel free to create a new thread in the typing forum. This is an area where backward compatibility is really important, so any such proposals will be reviewed with that in mind.
from typing.
I think it's unnecessary (and arguably inappropriate) for it to discuss PEP 3141, since it has no bearing on the type system.
I'm not sure I agree. PEP-3141 is a PEP that has been accepted for 17 years, and has not been deprecated. At runtime, Python recognises complex
as a "virtual subclass" of numbers.Complex
, float
as a virtual subclass of numbers.Real
and int
as a virtual subclass of numbers.Integral
. For any other stdlib module like this, we would either try to model this subtyping relationship in typeshed, or type checkers would implement special-casing to model it. It's reasonable for users to expect type checkers to understand the subtyping relationship that holds true at runtime, and it's a persistent source of surprise that type checkers do not.
I'm fully aware of the reasons why neither typeshed nor type checkers support PEP-3141, I don't believe that should change, and I don't want to restart a conversation about whether they should or not. But the decision by PEP-484 to state that PEP-3141 is a "runtime-only" numeric tower, and to instead implement a parallel "static-only" numeric tower that is understood by type checkers but is not understood by issubclass()
checks at runtime -- this is, in my view, a fairly crucial design choice of the type system.
from typing.
I was probably the person who cut out the mention of PEP 3141 from the spec. I agree with @erictraut above that a discussion of this topic is a better fit for the user-facing type system reference (which unfortunately we haven't yet done much work on). If all we are saying is that there are no special cases for type checkers, that doesn't feel like it's worth saying in the spec.
Eric's suggestion above about the interpretation of the float/int special case is interesting and it does seem to be a more sensible interpretation than treating int directly as a subtype of float.
from typing.
If all we are saying is that there are no special cases for type checkers
From my perspective, there is a special case when it comes to PEP-3141. For all other stdlib ABCs, we've taken pains in typeshed to ensure that type checkers understand runtime virtual subclasses of those ABCs as static subtypes of those ABCs. list
inherits from collections.abc.MutableSequence
in typeshed, even though it doesn't at runtime; set
inherits from collections.abc.MutableSet
in typeshed, even though it doesn't at runtime; we pretend os.PathLike
and contextlib.AbstractContextManager
are protocols in typeshed, even though at runtime they're just ABCs. It's only the numbers
ABCs where we deliberately don't try to model the subtyping relationship in typeshed. (And, again, I support that policy.)
from typing.
I thought about this a bit more, and unfortunately flexible types have the same drawbacks as existing special-casing -- they allow for unsoundness (unless supplemented with runtime type checking).
I wonder if the "right" thing to do here would be to abandon the idea of special-casing of int
, float
and complex
altogether? The runtime type of 42 is always an int
and never float
nor complex
. Why allow passing 42 to an API which requires a float
? Moreover, the fix is always obvious -- just add .0
or j
to your literal.
from typing.
unsoundness (unless supplemented with runtime type checking)
But we always have runtime type checking, right? AttributeError
, TypeError
, and so on are exactly that. So what's the unsoundness complaint about?
from typing.
But we always have runtime type checking, right? [...]
Raising AttributeError
is not the same as saying "you called function f
with an int
instead of a float
and that caused AttributeError
such and such".
Because Python users have been doing that for the last 30 years.
I agree that the type system should be designed around existing Python code, but given that it is probably impossible to account for every Python dynamic feature in a reasonable way, there will always be compromises.
Could you give an example which stops working if 42 is statically modelled as just int
with no subtyping/consistent subtyping connection to float
?
from typing.
This sounds like a trick question.
It was not intended as one, actually.
If we can guarantee that int
<: float
<: complex
structurally, then there is no issue with the original wording in PEP-484, because users cannot observe the difference between the types.
from typing.
If we can guarantee that
int
<:float
<:complex
structurally, then there is no issue with the original wording in PEP-484, because users cannot observe the difference between the types.
That would be my preferred approach (and what I had in mind when we created PEP 484, nearly 10 years ago).
from typing.
Related Issues (20)
- Out-of-date description of how to indicate positional-only parameters
- Incorporate PEP 483 into the spec HOT 5
- Conformance tests: Add mechanism to allow errors on one of multiple lines HOT 2
- Conformance tests: Add mechanism to ignore errors
- Conformance tests: Align automated and manual scoring HOT 1
- Introduce a `Language` type to provide consistent language information of strings. HOT 5
- Spec: Version and platform checking underspecified HOT 6
- Support list comprehension for `TypeVarTuple` HOT 2
- Clarify the float/int/complex special case HOT 5
- Clarify the implications of subclassing Any HOT 1
- Allow multiple `TypeVarTuple` in Generic Classes when they are wrapped. HOT 5
- pytype conformance tests crash if no `python3.11` executable is in `PATH` HOT 5
- Conformance test: make dataclass_hash.py not rely on `typing.Hashable`? HOT 1
- Proposal: Add coerced type narrowing similar to 'cast' HOT 1
- Idenity Type HOT 2
- [spec] clarify assert_type behavior as asserting type equivalence
- [spec] better clarify the difference between a runtime type object and a static type HOT 1
- [spec] a protocol type should be assignable to object? HOT 1
- [spec] define or replace the term "concrete type" HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typing.