协议实施者中的 Kwargs:什么是有效签名?

Kwargs in a Protocol implementer: what is a valid signature?

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

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 中的参数与协议 class 中的 **kwargs 兼容。

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

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

您对 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 并且你的实现能够处理任何事情,那将是允许。但一般来说,您会希望您的协议能够指导您实际想要采取的措施。)

总则

如果想让MyPy接受某个class实现了Protocol中定义的接口,具体实现中的相关方法必须不那么宽松 在参数中它将接受比在 Protocol 中定义的该方法的抽象版本。这与面向对象编程的其他原则一致,例如 Liskov Substitution Principle.

具体问题在这里

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

可能的解决方案

如果您希望 MyPy 将您的 class 声明为 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 中声明的方法的通用签名