On the one hand, I have learned that numbers that can be int
or float
should be type annotated as float
(sources: PEP 484 Type Hints and this stackoverflow question):
def add(a: float, b: float):
return a + b
On the other hand, an int
is not an instance of float
:
issubclass(int, float)
returns False
isinstance(42, float)
returns False
I would thus have expected Union[int, float]
to be the correct annotation for this use case.
Questions:
int
/float
a special case in type annotations? Are there other examples like this?Union[float, int]
if this is an unintended use?
- Are int/float a special case in type annotations?
float
is a special case. int
is not. PEP 484 says, in the paragraph below the one referenced by the link in your question:
when an argument is annotated as having type
float
, an argument of typeint
is acceptable;
So accepting int
where float
is annotated is explicitly a special case, independent of the way annotations generally deal with a class hierarchy.
Are there other examples like this?
Yes, there's at least one other special case. In that same paragraph PEP 484 goes on to say:
for an argument annotated as having type
complex
, arguments of typefloat
orint
are acceptable.
- Is there any linter that would warn me about
Union[float, int]
if this is an unintended use?
Union[float, int]
is perfectly fine.
The special treatment of a float
annotation is just a convenience (PEP 484 calls it a "shortcut") to allow people to avoid writing out the long-winded Union[float, int]
annotation, because arguments that can be a float
or an int
are very common.