109

mypy is really handy and catches a lot of bugs, but when I write "scientific" applications, I often end up doing:

def my_func(number: Union[float, int]):
    # Do something

number is either a float or int, depending on the user's input. Is there an official way to do that?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
JPFrancoia
  • 4,866
  • 10
  • 43
  • 73
  • Many of the answers below suggest to use `float` only because `int` gets implied. But that seems to be the case only when doing type hinting. What if I am *both* using it for type hinting and for determining actions in the function? Specifically, I am making up fake data using `faker`, and I want a wrapper function with a match statement that generates different fake data depending upon what the type is. I guess still `Union[float, int]`, right? – Mike Williamson May 09 '23 at 08:53

4 Answers4

179

Use float only, as int is implied in that type:

def my_func(number: float):

PEP 484 Type Hints specifically states that:

Rather than requiring that users write import numbers and then use numbers.Float etc., this PEP proposes a straightforward shortcut that is almost as effective: when an argument is annotated as having type float, an argument of type int is acceptable; similar, for an argument annotated as having type complex, arguments of type float or int are acceptable.

(Bold emphasis mine).

Ideally you would still use numbers.Real:

from numbers import Real

def my_func(number: Real):

as that would accept fractions.Fraction() and decimal.Decimal() objects as well; the number pyramid is broader than just integers and floating point values.

However, these are not currently working when using mypy to do your type checking, see Mypy #3186.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 9
    Tangentially related, but `Decimal` is not actually a subclass of `Real`, or part of the numeric tower at all: https://www.python.org/dev/peps/pep-3141/#the-decimal-type – user1475412 Jun 21 '19 at 15:32
  • 1
    @user1475412 — interestingly, `decimal.Decimal` is now registered as a virtual subclass of `numbers.Number` (though still isn't a virtual subclass of `numbers.Real`). I think the decision to add it as a virtual subclass must have been made some time after the PEP was written. https://github.com/python/cpython/blob/6846d6712a0894f8e1a91716c11dd79f42864216/Lib/_pydecimal.py#L3849 – Alex Waygood Oct 08 '21 at 09:57
  • @AlexWaygood: I think you misundrestood something; `decimal.Decimal` has been registered as a `Number` for [13 years now](https://github.com/python/cpython/commit/82417ca9b257133e37e5339b29f67160ad568722), well before the PEP. It still isn't a virtual subclass of `numbers.Real`, which is what user1475412 is talking about. – Martijn Pieters Oct 29 '21 at 12:34
  • @MartijnPieters, I'm not sure I understand your point. The GitHub link you posted shows that `decimal.Decimal` was registered as a subclass of `Number` in 2009, but PEP 3141 was written in 2007. So I'm not sure how you can argue that `decimal.Decimal` was registered as a `Number` prior to the PEP being written and accepted. And the very first draft of `numbers.Number` shows the module was only created in response to the acceptance of PEP 3141: https://github.com/python/cpython/commit/1daf954dcf6fa5630b049f1d1edcc9cc656ea5be#diff-0a6f4412944ff4213196a2efc88938539129c03c5787c02ce9900f8fb92d0328 – Alex Waygood Oct 29 '21 at 15:08
  • I'm aware that `Decimal` is not a virtual subclass of `numbers.Real`, but user1475412 also said: "`Decimal` is not actually [...] part of the numeric tower at all", paraphrasing PEP 3141. I interpreted that line in the PEP to mean that the authors of the PEP did not intend for `Decimal` to be registered as a virtual subclass of *any* of the `numbers` classes at that point in time. Am I wrong in that interpretation? – Alex Waygood Oct 29 '21 at 15:10
  • Imho for the type checking of the function **locally** it can still make sense to use an explicit `Union[int, float]`. In general, `int` and `float` are different entities, and if the local code needs to differentiate between the two, the type union can be useful. Look e.g. at `dir(1)` vs `dir(1.0)`. If you need any of the member functions that are int/float specific, the explicit type union makes sense for type-checking the code locally. – bluenote10 Dec 28 '21 at 19:52
10

Python > 3.10 allows you to do the following.

def my_func(number: int | float) -> int | float: 
SuperNova
  • 25,512
  • 7
  • 93
  • 64
  • 2
    As Martijn's answer points out, the mypy documentation clearly states that "when an argument is annotated as having type float, an argument of type int is acceptable". Explicitly annotating the union here is pointless. – Alex Waygood Oct 08 '21 at 09:53
  • 6
    @AlexWaygood While that statement is true in a majority of cases, it is wrong to say that the type union in pointless in general. `int` and `float` are different things in general, and if you code needs to differentiate between these differences locally, the type union is appropriate. Look e.g. at `dir(1)` vs `dir(1.0)`. If you need any of the member functions that are defined on either one, you need the type union so that the code typechecks correctly. – bluenote10 Dec 28 '21 at 19:42
  • 1
    @bluenote10 thanks for the correction; you're right, my comment was too strongly worded. – Alex Waygood Dec 29 '21 at 01:14
9

You can define your own type to address this and keep your code cleaner.

FloatInt = Union[float, int]

def my_func(number: FloatInt):
    # Do something
Curt Welch
  • 339
  • 3
  • 5
  • 3
    Good idea, but `int` is officially redundant when `float` is present. See this answer: https://stackoverflow.com/a/50928627/4960855 – EliadL Mar 30 '21 at 09:28
-2

For people who come to this question for the more general problem of Union typing hints for entities which don't have an existing supertype in common, for example Union[int, numpy.ndarray], the solution is to import Union from typing.

Example 1:

from typing import Union

def my_func(number: Union[float, int]):
    # Do something

Example 2:

from typing import Union
import numpy as np

def my_func(x: Union[float, np.ndarray]):
    # do something
    # Do something
patapouf_ai
  • 17,605
  • 13
  • 92
  • 132
  • I guess you probably meant `np.number` instead of `np.ndarray` – Jongwook Choi Oct 18 '22 at 22:04
  • @JongwookChoi no, I didn't. I specifically write the case where I want to deal with floats and numpy arrays, it's not worth a separate question as the question is related enough. np.number already includes float, so there is no need to use Union. – patapouf_ai Oct 24 '22 at 17:16
  • I can see your specific use cases, but I feel it's a bit off-topic as the OP asks a question to represent either float and int. – Jongwook Choi Oct 25 '22 at 04:51
  • 1
    I can see your opinion, but I see it differently, I can to this question when searching how trying to represent float and arrays, which is why I am answering both the original question and also my specific use-case. – patapouf_ai Oct 25 '22 at 04:56