У меня есть следующая настройка:
@pytest.fixture
def check_answer() -> t.Callable[[dict[str, t.Any], dict[str, t.Any], int], None]:
def _check(
first_response: dict[str, t.Any],
second_response: dict[str, t.Any],
named_optional_arg: int = 1,
) -> None:
*assert stuff*
return _check
Затем я передаю check_answer
в качестве параметра test_my_answer
и хочу назвать его check_answer(ans1, ans2, named_optional_arg=5)
. Необязательный аргумент используется очень редко, поэтому важно, чтобы имя присутствовало при его вызове, однако, когда я включаю имя, я получаю ошибку от mypy из-за неожиданного ключевого аргумента.
Проблема в том, что ваша сигнатура типа недостаточно конкретна, чтобы отразить тот факт, что _check
имеет named_optional_arg
. Callable
недостаточно для ввода аргументов ключевых слов, поэтому необходимо использовать протоколы обратного вызова:
from collections.abc import Mapping
from typing import Protocol, Any
class Checker(Protocol):
def __call__(
self,
first_response: Mapping[str, Any],
second_response: Mapping[str, Any],
/,
*,
named_optional_arg: int = ...
):
...
Вы можете прочитать Что делают * (одинарная звезда) и / (косая черта) как независимые параметры? для получения дополнительной информации о том, как /
и *
можно использовать в списке параметров для описания точного способа вызова функции. В этой ситуации я сделал так, что первые два аргумента должны быть указаны по позиции, а третий необязательный аргумент должен быть указан по имени.
Кроме того, обратите внимание, что я заменил dict на Mapping. Это связано с тем, что проверка/утверждение требует только чтения, а не записи ответов.
Затем вы измените свое объявление на:
@pytest.fixture
def check_answer() -> Checker:
...