如何为可调用任意数量任意类型关键字参数的类型定义 Python 协议?
How do I define a Python Protocol for a type that is Callable with any number of keyword arguments of Any type?
如何为以下类型定义 Python 协议:
- 可调用
- 任意数量任意类型的关键字参数
- 那个returns一个指定类型的值
这是我的尝试:
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]
参见:
如何为以下类型定义 Python 协议:
- 可调用
- 任意数量任意类型的关键字参数
- 那个returns一个指定类型的值
这是我的尝试:
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]
参见: