Как ограничить функцию для работы только с числовыми типами данных в Python?
Что проверяют
Различаете ли вы статическую и рантайм-проверку типов и знаете ли, что в Python считается числом. Следом обычно спрашивают, пропустит ли ваша проверка True, Decimal и числа из NumPy.
Как отвечать
Сначала уточните, что нужно: подсказка для анализатора или отказ во время выполнения. Это разные инструменты.
- аннотации:
def scale(x: float) -> float. По числовой иерархии из PEP 484 аннотацияfloatпринимает иint. Проверяют это mypy или pyright, интерпретатор аннотации не исполняет; - обобщённая функция: чтобы тип результата совпадал с типом аргумента, нужен ограниченный
TypeVar, в Python 3.12 это пишется какdef scale[T: (int, float)](x: T) -> T; - рантайм через ABC:
isinstance(x, numbers.Real)пропускаетint,float,Fractionи скаляры NumPy, которые зарегистрированы в этих ABC.numbers.Numberшире и пропустит ещёcomplexиDecimal; - отсечь
bool:boolнаследуетint, поэтомуisinstance(True, int)истинно. Если логические значения недопустимы, их исключают отдельной проверкой; - валидация на границе: декоратор
validate_callиз Pydantic v2 проверяет и приводит аргументы по аннотациям, есть также beartype и typeguard; - разные реализации: если для разных типов нужна своя логика, подходит
functools.singledispatch.
Хороший ответ заканчивается мыслью про утиную типизацию: часто правильнее не проверять тип, а выполнить операцию и обработать TypeError. Жёсткую проверку ставят на входе публичного API, а не в каждой внутренней функции.
Где спотыкаются
Проверяют isinstance(x, (int, float)). Такая проверка пропускает True, но отвергает Decimal, Fraction и numpy.int64, который не наследует int.
Думают, что аннотация x: int запретит передать строку. Без внешнего инструмента во время выполнения она ничего не проверяет.
Пишут type(x) == int. Это ломает наследование и не спасает от float("nan"), которое проходит любую проверку типа, хотя по смыслу числом не является.