【问题标题】:Kwargs in a Protocol implementer: what is a valid signature?协议实现者中的 Kwargs:什么是有效签名?
【发布时间】:2021-10-16 22:34:25
【问题描述】:

我的问题很简单。我有这个协议:

from typing import Protocol

class LauncherTemplateConfig(Protocol):
    def launch_program_cmd(self, **kwargs) -> list[str]:
        pass

这个协议的实现,我希望 mypy 通过,但它没有:

from typing import Optional
from pathlib import Path

class MyLauncherTemplateConfig:
    def launch_program_cmd(
        self, some_arg: Optional[Path] = None, another_arg=1
    ) -> list[str]:

我希望MyLauncherTemplateConfig.launch_program_cmd 中的参数与Protocol 类中的**kwargs 兼容。

不确定我是否做错了什么......

【问题讨论】:

    标签: python protocols type-hinting mypy python-typing


    【解决方案1】:

    关键问题是你的宽容是颠倒的。形式上,这是因为函数的输入是逆变的。

    您对def launch_program_cmd(self, **kwargs) -> list[str]: 的承诺是“此方法将能够采用任何一组关键字参数。”

    举个例子,如果有人写

    def launch_lunch(launcher: LauncherTemplateConfig):
        launcher.launch_program_cmd(food=["eggs", "spam", "spam"])
    

    那么根据LauncherTemplateConfig的定义应该是允许的。

    但是,如果您尝试使用 MyLauncherTemplateConfig 的实例调用该方法,那么它将崩溃,因为它不知道如何处理 food 参数。所以MyLauncherTemplateConfig 不是LauncherTemplateConfig 的有效子类型

    我怀疑你想要传达的更像是“这种方法会存在,但我不知道它会采取什么论据。”然而,这并不是 MyPy 真正要表达的东西。根本原因是它不是很有用:你可以用一个方法将存在但你不知道如何调用它的承诺来做很多事情!

    (注意:相反的方向是允许的。如果您的协议指定您必须能够采用some_arganother_arg 并且您的实现能够处理任何事情,那将是允许的。但通常,您'希望您的协议能够指导您实际想要采取的措施。)

    【讨论】:

    • 事实上我不知道参数,这就是为什么我在基类中使用 **kwargs ,意思是:“我的协议实现类有一个带有任意数量的关键字参数的签名”。这些会在以后的运行时检查,因为我动态地使用这些参数。
    • 那是你的问题。如果您说实现类可以处理任何事情,那您就可以了。类似于协议中的def launch_program_cmd(self, *args: Any, **kwargs: Any): 和实现中的def launch_program_cmd(self, some_arg: Optional[Path] = None, another_arg=1, *args: Any, some_kwarg: float=1.2, **kwargs: Any):
    • 请注意,如果协议中没有 *args,则只能添加关键字参数,如果协议中没有 **kwargs,则只能添加位置参数。跨度>
    【解决方案2】:

    一般原则

    如果您希望 MyPy 接受某个类实现了在 Protocol 中定义的接口,那么具体实现中的相关方法在其将接受的参数中必须不低于 Protocol 中定义的该方法的抽象版本。这与诸如Liskov Substitution Principle 等面向对象编程的其他原则是一致的。

    这里的具体问题

    您的Protocol 定义了一个接口,其中launch_program_cmd 方法可以使用any 关键字参数调用,并且在运行时不会失败。您的具体实现不满足此接口,因为除some_arganother_arg 之外的任何关键字参数都会导致该方法引发错误。

    可能的解决方案

    如果您希望 MyPy 将您的类声明为您的 Protocol 的安全实现,您有两种选择。您可以将Protocol 中的方法签名调整为更具体,也可以将具体实现中的方法签名调整为更通用。对于后者,您可以这样做:

    from typing import Any, Protocol, Optional
    from pathlib import Path
    
    class LauncherTemplateConfig(Protocol):
        def launch_program_cmd(self, **kwargs: Any) -> list[str]: ...
    
    
    class MyLauncherTemplateConfig:
        def launch_program_cmd(self, **kwargs: Any) -> list[str]:
            some_arg: Optional[Path] = kwargs.get('some_arg')
            another_arg: int = kwargs.get('another_arg', 1)
            # and then the rest of your method
    

    通过使用dict.get 方法,我们可以在您的实现中保留默认值,但要坚持Protocol 中声明的方法的通用签名

    【讨论】:

    • 谢谢。这是我考虑走的路。我会接受,因为没有其他办法,因为签名实际上是有点“静态”的。否则会违反该原则,是的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-29
    • 2011-04-28
    • 1970-01-01
    相关资源
    最近更新 更多