如何为可调用任意数量任意类型关键字参数的类型定义 Python 协议?

How do I define a Python Protocol for a type that is Callable with any number of keyword arguments of Any type?

如何为以下类型定义 Python 协议:

这是我的尝试:

from typing import Any, Protocol, TypeVar

T = TypeVar("T", covariant=True)


class Operation(Protocol[T]):
    def __call__(self, **kwargs: Any) -> T:
        pass


# some example functions that should be a structural sub-type of "Operation[str]"
def sumint(*, x: Any, y: Any) -> str:
    return f"{x} + {y} = {x + y}"


def greet(*, name: Any = "World") -> str:
    return f"Hello {name}"


# an example function that takes an "Operation[str]" as an argument
def apply_operation(operation: Operation[str], **kwargs: Any) -> str:
    return operation(**kwargs)


if __name__ == "__main__":
    print(apply_operation(sumint, x=2, y=2))
    # prints: 2 + 2 = 4
    print(apply_operation(greet, name="Stack"))
    # prints: Hello Stack

然而,mypy 产生错误:

example.py:26: error: Argument 1 to "apply_operation" has incompatible type "Callable[[NamedArg(Any, 'x'), NamedArg(Any, 'y')], str]"; expected "Operation[str]"
example.py:28: error: Argument 1 to "apply_operation" has incompatible type "Callable[[DefaultNamedArg(Any, 'name')], str]"; expected "Operation[str]"
Found 2 errors in 1 file (checked 1 source file)

我做错了什么?如何让 MyPy 开心?

关于 MyPy 为何不满意的确切原因,我无法回答您的问题 — 但 MyPy 似乎对另一种方法感到满意:

from typing import Any, Callable, TypeVar

T = TypeVar("T", covariant=True)


Operation = Callable[..., T]


# some example functions that should be a structural sub-type of "Operation[str]"
def sumint(*, x: int = 1, y: int = 2) -> str:
    return f"{x} + {y} = {x + y}"


def greet(*, name: str = "World") -> str:
    return f"Hello {name}"


# an example function that takes an "Operation[str]" as an argument
def apply_operation(operation: Operation[str], **kwargs: Any) -> str:
    return operation(**kwargs)


if __name__ == "__main__":
    print(apply_operation(sumint, x=2, y=2))
    # prints: 2 + 2 = 4
    print(apply_operation(greet, name="Stack"))
    # prints: Hello Stack

您无法定义满足您要求的协议,因为从静态类型的角度来看,它根本上是不安全的。

这里的问题是,尽管您说 Operation[T] 的实例应该可以“使用任何类型的任意数量的关键字参数”调用,但您似乎实际上是说 一些 它接受的关键字参数的组合。它不仅接受任何关键字参数。

如果您可以定义具有您想要的特征的 Operation[T] 协议,那么您的 apply_operation

def apply_operation(operation: Operation[str], **kwargs: Any) -> str:
    return operation(**kwargs)

仍然是类型错误。 operation(**kwargs) 是不安全的,因为不能保证提供的关键字参数是 operation 接受的参数。你可以打电话

apply_operation(sumint, name="Stack")

这符合 apply_operation 签名,但仍然是不安全的调用。


如果您想对此进行注释,最好的选择可能是使用 Callable[..., T],如 Alex Waygood 的回答中所建议的那样。将 ... 指定为 Callable 的参数类型列表有效地禁用了可调用参数的类型检查,就像使用 Any 注释变量有效地禁用了该变量的类型检查一样。

请记住,这 会禁用 安全检查 - 如果您执行类似 apply_operation(sumint, name="Stack").

的操作,将不会有任何警告

按照其他答案中的建议使用Callable[..., T],解决了问题中的问题。

如果仍然需要实现自定义 Protocol(例如,如果需要向协议中添加其他方法),可以通过以下方式完成:

from typing import Any, Protocol, TypeVar

T = TypeVar("T", covariant=True)

class Operation(Protocol[T]):
    __call__: Callable[..., T]

参见: