【问题标题】:How do I define a Python Protocol for a type that is Callable with any number of keyword arguments of Any type?如何为具有任意数量的 Any 类型关键字参数的 Callable 类型定义 Python 协议?
【发布时间】:2021-07-24 21:49:27
【问题描述】:

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

【问题讨论】:

    标签: python python-3.x mypy


    【解决方案1】:

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

    这里的问题是,尽管您说 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 忽略这些论点是我真正想要做的。我知道这是不安全的,但在我的实际应用程序中,如果发生异常,我将处理它们。我认为Callable[..., T] 是解决我的问题的最佳方法。非常感谢您的帮助。
    • 谢谢,这是一个有用的解释,对我的回答有一些很好的警告。
    【解决方案2】:

    我无法回答您关于 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
    

    【讨论】:

      【解决方案3】:

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

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

      from typing import Any, Protocol, TypeVar
      
      T = TypeVar("T", covariant=True)
      
      class Operation(Protocol[T]):
          __call__: Callable[..., T]
      
      

      见:How to combine a custom protocol with the Callable protocol?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-17
        相关资源
        最近更新 更多