如何为以下类型定义 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)
我做错了什么?我怎样才能让我的孩子开心?
您无法定义适合您要求的协议,因为从静态类型的角度来看它根本上是不安全的。
这里的问题是,尽管您说
Operation[T]
的实例应该可以“使用任意数量的任何类型的关键字参数”进行调用,但您似乎实际上意味着它接受 some 的关键字参数组合。它不仅仅接受任何关键字参数。
如果您可以定义具有您想要的特征的
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")
的操作,将不会发出警告。
我无法确切地回答你为什么 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
按照其他答案中的建议,使用
Callable[..., T]
解决了问题中的问题。
如果仍然需要实现自定义
Protocol
(例如,如果需要将其他方法添加到协议中),可以通过以下方式完成:
from typing import Any, Protocol, TypeVar
T = TypeVar("T", covariant=True)
class Operation(Protocol[T]):
__call__: Callable[..., T]