The exact language from PEP 484 is
when an argument is annotated as having type
float
, an argument of typeint
is acceptable; similar, for an argument annotated as having typecomplex
, arguments of typefloat
orint
are acceptable.
There is also this example from the “Subtype relationships” section of PEP 483
A more formal example: Integers are subtype of real numbers. Indeed, every integer is of course also a real number, and integers support more operations, such as, e.g., bitwise shifts
<<
and>>
:lucky_number = 3.14 # type: float lucky_number = 42 # Safe lucky_number * 2 # This works lucky_number << 5 # Fails unlucky_number = 13 # type: int unlucky_number << 5 # This works unlucky_number = 2.72 # Unsafe
(note: variable annotations were not supported until later with PEP 526, so this is using the special type comments)
From those two, it’s clear that the behavior of a type checker should be that an int
can be assigned to a variable annotated as float
or passed to a function argument annotated as float
, but it doesn’t actually specify how that behavior should be implemented.
mypy uses heuristics that kind of treat int
as a subclass of float
but not always, while pyright moved to mostly doing the approach of “float
in a type expression actually means float | int
”
There had already been an issue on the mypy issue tracker that suggested moving to that union approach. Carl Meyer (who has been working on Astral’s new type checker ty
) opened an issue on the typing
GitHub asking that the typing specification be clarified.
Jelle Zijlstra proposed that the typing specification be modified to explicitly specify the union approach, which mostly matches existing type checker results and the conformance test suite that goes along with the typing specification. He then opened a thread here on Discuss about that change, which then prompted this very discussion thread.