Formally, type NP should be narrowed to the intersection of A and R, and type NN should be narrowed to the intersection of A and the complement of R. In practice, the theoretic types for strict type guards cannot be expressed precisely in the Python type system. Type checkers should fall back on practical approximations of these types. As a rule of thumb, a type checker should use the same type narrowing logic – and get results that are consistent with – its handling of isinstance().
Matching the behavior of isinstance is only a rule of thumb, not the actual definition.
Yes they have argued that (and “they” here literally includes the designer of the language). int being a structural subtype of float has been the design intent of Python from the very beginning.
Quoting a handful of comments by Guido van Rossum on a related GitHub issue
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.
Why allow passing 42 to an API which requires a float?
Because Python users have been doing that for the last 30 years.
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).
Python the language has been designed carefully to allow mixing floats and ints at runtime (some corner cases notwithstanding).
The intent is currently and has always been that int should be considered a subtype of float, regardless of the fact that isinstance(0, float) returns False
This was one of the original decisions about how typing should work in Python, has been in effect for over a decade, and reflects what the expected semantics have been for over 30 years (longer than I personally have been alive).
The issue that is being addressed is that it is not clear how that intent should be written in an unambiguous way in the formal specification that all type checker implementations should adhere to and can be tested for conformance against.
The current proposal on hand is to clarify that the way that structural subtype relationship should be handled across type checker implementations is that the type annotation float has the same meaning as the union of runtime floats and runtime ints.
This matches the current actual implementation behavior of every major type checker.
Disagreeing with the premise that int is a subtype of float does not help clarify how the formal specification should be written