10

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:

  • What is the reason for that counter-intuitive behaviour? Does type hinting follow different mechanics than class comparisons (for instance in some case a "lossless casting" rule or so)?
  • Are int/float a special case in type annotations? Are there other examples like this?
  • Is there any linter that would warn me about Union[float, int] if this is an unintended use?
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
matheburg
  • 2,097
  • 1
  • 19
  • 46

1 Answers1

10
  • 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 type int 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 type float or int 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.

Community
  • 1
  • 1
ottomeister
  • 5,415
  • 2
  • 23
  • 27
  • 1
    Yes pythons type annotations do make pragmatic compromises. Another example is annotating with `None` when it should be `NoneType` or something – juanpa.arrivillaga Jul 04 '20 at 22:25
  • 1
    Thanks! So my fallacy lies in the assumption that `float` should be preferred over `Union[int, float]`. It is totally fine that I use the more explicit `Union[int, float]` - right? :) – matheburg Jul 05 '20 at 06:54