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 году, так что тогда вы сможете избавиться от перегрузок.

Другие вопросы по тегам