Python 3 печатает varadic определения "применять" стиля
Я изо всех сил пытался написать "varadic" определения типов списков аргументов.
например, давая типы:
def foo(fn, *args):
return fn(*args)
лучшее, что я смог сделать, это использовать предложение отсюда:
from typing import overload, Callable, TypeVar
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
R = TypeVar('R')
@overload
def foo(fn: Callable[[A], R], a: A) -> R: ...
@overload
def foo(fn: Callable[[A, B], R], a: A, b: B) -> R: ...
@overload
def foo(fn: Callable[[A, B, C], R], a: A, b: B, c: C) -> R: ...
def foo(fn, *args):
return fn(*args)
который в основном делает правильные вещи... например, учитывая:
def bar(i: int, j: int) -> None:
print(i)
следующее успешно:
foo(bar, 10, 12)
пока они терпят неудачу:
foo(bar, 10)
foo(bar, 10, 'a')
foo(bar, 10, 12) + 1
но если я проверю с mypy --strict
Я получил:
test.py:15: error: Function is missing a type annotation
(который говорит, что финал foo
само определение не имеет никаких типов)
Я могу переопределить foo
быть:
def foo(fn: Callable[..., R], *args: Any) -> R:
return fn(*args)
но потом, когда я бегу mypy --strict
Я получил:
test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 1
test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 2
test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 3
что я не очень понимаю.
если кто-то может предложить лучший способ предоставления типов для такого рода функций, это будет очень цениться! если бы я мог сделать это, не перечисляя много overload
Если бы это было хорошо, в реальных определениях также есть несколько аргументов "только для ключевых слов", которые было бы неплохо не повторять каждый раз
1 ответ
Причина, по которой вы получаете сообщение об ошибке "Реализация перегруженной функции не принимает все возможные аргументы...", заключается в том, что ваша реализация перегрузки неправильно обрабатывает вызовы, которые выглядят так: foo(my_callable, a=3, b=4)
,
В конце концов, согласно вашим сигнатурам перегрузки, пользователь теоретически может явно использовать именованные аргументы для a, b, c и т. Д., И поэтому ваша реализация должна поддерживать такие вызовы.
Это можно исправить двумя способами.
Первый способ заключается в **kwargs: Any
и измените вашу реализацию перегрузки, чтобы она выглядела так:
def foo(fn: Callable[..., R], *args: Any, **kwargs: Any) -> Any:
return fn(*args, **kwargs)
Теперь ваша реализация будет правильно обрабатывать такие вызовы.
Второй способ заключается в добавлении к каждому из ваших параметров двух символов подчеркивания, например:
@overload
def foo(fn: Callable[[A], R], __a: A) -> R: ...
@overload
def foo(fn: Callable[[A, B], R], __a: A, __b: B) -> R: ...
@overload
def foo(fn: Callable[[A, B, C], R], __a: A, __b: B, __c: C) -> R: ...
def foo(fn: Callable[..., R], *args: Any) -> Any:
return fn(*args)
Когда mypy видит параметр, начинающийся с двух подчеркиваний, он понимает, что аргумент должен быть только позиционным. Таким образом, Mypy будет отклонять звонки, как foo(my_fn, __a=3, __b=4)
,
Это только для набора текста. Префикс ваших параметров с двумя подчеркиваниями не имеет особого значения во время выполнения.
Относительно вашего более широкого вопроса о том, что вам не нужно повторять так много перегрузок: к сожалению, преодоление множества перегрузок - лучшее, что мы можем сделать на данный момент. Техника, которую вы используете - это та же техника, которую использует типизированная для ввода таких функций, как map(...)
а также filter(...)
, например.
Чтобы добиться большего, нам нужна функция под названием variadic generics - но она сложная и mypy, к сожалению, пока их не поддерживает. Мы надеемся, что их реализация будет реализована где-то позже, в 2019 году, так что тогда вы сможете избавиться от перегрузок.