54

Forgive me if this question has been asked before but I could not find any related answer.

Consider a function that takes a numerical type as input parameter:

def foo(a):
    return ((a+1)*2)**4;

This works with integers, floats and complex numbers.

Is there a basic type so that I can do a type hinting (of a real existing type/base class), such as:

def foo(a: numeric):
    return ((a+1)*2)**4;

Furthermore I need to use this in a collection type parameter, such as:

from typing import Collection;
def foo(_in: Collection[numeric]):
    return ((_in[0]+_in[1])*2)**4;
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
roschach
  • 8,390
  • 14
  • 74
  • 124

3 Answers3

66

PEP 3141 added abstract base classes for numbers, so you could use:

from numbers import Number

def foo(a: Number) -> Number:
    ...
norok2
  • 25,683
  • 4
  • 73
  • 99
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • 4
    just be careful, because `Number` includes `Complex`: `isinstance(0+1j, Number)` is `True` – Walter Tross May 15 '20 at 20:26
  • right, read it too hastily, but just like me many more will, so I will leave this caveat here – Walter Tross May 15 '20 at 20:28
  • 9
    When I call `foo(1)`, mypy complains `foo has incompatible type "int"; expected "Number"`. In other words: this answer doesn't work for mypy users. See also this open [mypy issue](https://github.com/python/mypy/issues/3186). – normanius Apr 21 '21 at 17:27
  • 4
    The same is the case for `foo(np.int32(1))`, by the way. mypy raises a similar error: `Argument 1 to "foo" has incompatible type "floating[_32Bit]"; expected "Number"`. Any suggestions what mypy users should do? – normanius Apr 21 '21 at 17:43
24

There isn't a generic numeric type in the typing module, so you would have to create such a type with Union instead:

from typing import Union

numeric = Union[int, float, complex]

...

To add support for Numpy's collection of numeric types, add np.number to that Union.

numeric = Union[int, float, complex, np.number]
normanius
  • 8,629
  • 7
  • 53
  • 83
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • 3
    Note that this won't work for numpy dtypes, but the accepted answer does. – sturgemeister Feb 13 '21 at 07:16
  • 2
    @sturgemeister I'm a bit confused, because `foo(np.float32(42))` will cause mypy (the static type checker I'm using) to issue `Argument 1 to "foo" has incompatible type "floating[_32Bit]"; expected "Number"`. With what type checker did you verify your statement? – normanius Apr 21 '21 at 17:35
  • Suggestion: use `numeric = Union[int, float, complex, np.number]`, this will also work numpy dtypes. – normanius Apr 21 '21 at 17:50
  • @normanius with base python, `isinstance(4, Number) and isinstance(np.ones((1,))[0], Number)` returns true, but this seems to be a high priority mypy issue, seems it's difficult to resolve https://github.com/python/mypy/issues/3186 – sturgemeister Apr 22 '21 at 18:34
  • @sturgemeister Thanks! That issue came to my attention as well. Note that type constructs from the typing module cannot be used for dynamic type checking. Something like `isinstance(4.2, numeric)` will cause a TypeError. – normanius Apr 23 '21 at 14:29
6

The currently accepted solution of using Number is fairly broken considering that, as pointed out in the comments, ints are not Numbers for static type checkers like mypy and PyRight. The situation has been discussed for years with no clear resolution.

Another possible approach extracted from a detailed explanation from a related question is:

from typing import SupportsFloat as Numeric

which has the following behavior:

from decimal import Decimal
from fractions import Fraction
from typing import SupportsFloat as Numeric

import numpy as np


def f(x: Numeric) -> None:
    pass


# Accepted by mypy/Pyright:
f(123)
f(np.uintc(55))
f(Fraction(-3, 2))
f(Decimal("-3.14"))
f(np.array([1, 2, 3]))  # Should an array be numeric?

# Results in type errors:
f(complex(2, 3))
f("asdf")

This has the advantage of being fairly permissive, except for complex. In case you want to include complex as well, simply do

from typing import SupportsFloat, Union

Numeric = Union[SupportsFloat, complex]

or equivalently in Python ≥3.10 style:

from typing import SupportsFloat, TypeAlias

Numeric: TypeAlias = SupportsFloat | complex

It's perhaps unfortunate that NumPy arrays are considered numeric in the sense of SupportsFloat, but that illustrates the obscure philosophical nature of the question "what is a number?".

Major disadvantage:

As noted by @lkwbr, the SupportsFloat type is only really suitable for identifying numbers. It fails to support any operators like addition or comparison.

For instance, Pyright gives the following error:

Operator "<" not supported for types "SupportsFloat" and "SupportsFloat"

Ben Mares
  • 1,764
  • 1
  • 15
  • 26
  • This generates Pylance linting errors for comparison operators. @blhsing's answer functions better in this regard. – lkwbr Feb 22 '23 at 08:54
  • 1
    Thanks @lkwbr, you're absolutely correct, and I've updated my answer accordingly. – Ben Mares Feb 23 '23 at 09:27