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

问题描述 投票:0回答:3

如何为以下类型定义 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)

我做错了什么?我怎样才能让我的孩子开心?

python protocols python-typing mypy keyword-argument
3个回答
4
投票

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

这里的问题是,尽管您说

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")
的操作,将不会发出警告。


3
投票

我无法确切地回答你为什么 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

2
投票

按照其他答案中的建议,使用

Callable[..., T]
解决了问题中的问题。

如果仍然需要实现自定义

Protocol
(例如,如果需要将其他方法添加到协议中),可以通过以下方式完成:

from typing import Any, Protocol, TypeVar

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

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

参见:如何将自定义协议与 Callable 协议结合起来?

© www.soinside.com 2019 - 2024. All rights reserved.