Введите аннотацию для Callable, которая принимает ** kwargs - PullRequest
2 голосов
/ 03 мая 2020

Существует функция ( f ), которая использует сигнатуру функции ( g ), которая принимает известный первый набор аргументов и любое количество аргументов ключевого слова **kwargs. Есть ли способ включить **kwargs в сигнатуру типа ( g ), которая описана в ( f )?

Например:

from typing import Callable, Any
from functools import wraps
import math


def comparator(f: Callable[[Any, Any], bool]) -> Callable[[str], bool]:
    @wraps(f)
    def wrapper(input_string: str, **kwargs) -> bool:
        a, b, *_ = input_string.split(" ")
        return f(eval(a), eval(b), **kwargs)

    return wrapper


@comparator
def equal(a, b):
    return a == b


@comparator
def equal_within(a, b, rel_tol=1e-09, abs_tol=0.0):
    return math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)


# All following statements should print `True`
print(equal("1 1") == True)
print(equal("1 2") == False)
print(equal_within("5.0 4.99998", rel_tol=1e-5) == True)
print(equal_within("5.0 4.99998") == False)

Функция comparator упаковывает свой аргумент f с wrapper, который использует ввод для f в виде строки, анализирует ее и оценивает это с использованием f. В этом случае Pycharm выдает предупреждение, что return f(eval(a), eval(b), **kwargs) вызывает f с неожиданным аргументом **kwargs, который не соответствует ожидаемой подписи.

Этот пост в Reddit предлагает добавление Any или ... к сигнатуре типа f, например

  • f: Callable[[Any, Any, ...], bool]
  • f: Callable[[Any, Any, Any], bool]

Первый вызывает ошибку TypeError [1], в то время как последняя, ​​похоже, вводит в заблуждение, поскольку f принимает не менее 2 аргументов, а не ровно 3.

Другой обходной путь - оставить Callable Аргументы определения открываются с ..., как f: Callable[..., bool], но мне интересно, есть ли более подходящее решение.

  1. TypeError: Callable[[arg, ...], result]: each arg must be a type. Got Ellipsis.

1 Ответ

1 голос
/ 04 мая 2020

tl; dr: Protocol может быть ближайшей реализованной функцией, но ее все еще недостаточно для того, что вам нужно. Подробнее см. в этом выпуске .


Полный ответ:

Я думаю, что наиболее близкая особенность к тому, о чем вы просите, Protocol, который был введен в Python 3.7 (и перенесен на 3,6 через typing_extensions). Это позволяет вам определить подкласс Protocol, который описывает поведение типа, во многом как «интерфейс» или «черта» в других языках. Для функций поддерживается аналогичный синтаксис:

from typing import Protocol
# from typing_extensions import Protocol  # if you're using Python 3.6

class MyFunction(Protocol):
    def __call__(self, a: Any, b: Any, **kwargs) -> bool: ...

def decorator(func: MyFunction):
    ...

@decorator  # this type-checks
def my_function(a, b, **kwargs) -> bool:
    return a == b

В этом случае любая функция, имеющая совпадающую сигнатуру, может соответствовать типу MyFunction.

Однако этого недостаточно для ваши требования. Чтобы сигнатуры функции совпадали, функция должна иметь возможность принимать произвольное количество аргументов ключевого слова (т. Е. Иметь аргумент **kwargs). На данный момент до сих пор нет способа указать, что функция может (необязательно) принимать любые ключевые аргументы. В этом выпуске GitHub обсуждаются некоторые возможные (хотя и многословные или сложные) решения в соответствии с текущими ограничениями.


На данный момент я бы предложил использовать Callable[..., bool] в качестве аннотации типа для f. Тем не менее, можно использовать Protocol для уточнения возвращаемого типа оболочки:

class ReturnFunc(Protocol):
    def __call__(self, s: str, **kwargs) -> bool: ...

def comparator(f: Callable[..., bool]) -> ReturnFunc:
    ....

Это избавит от ошибки «неожиданный аргумент ключевого слова» в equal_within("5.0 4.99998", rel_tol=1e-5).

...