我的问题很简单。我有这个协议:
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
中的参数与协议类中的**kwargs
兼容。
不确定我是否做错了什么...
一般原则
如果您希望 MyPy 接受某个类实现了
Protocol
中定义的接口,则具体实现中的相关方法必须在其接受的参数中不低于所定义的该方法的抽象版本。在Protocol
。这与面向对象编程的其他原则一致,例如Liskov替换原则。
具体问题在这里
您的Protocol
定义了一个接口,其中可以使用any
关键字参数调用
launch_program_cmd
方法,并且在运行时不会失败。您的具体实现不满足此接口,因为除 some_arg
或
another_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
中声明的方法的通用签名。
您对
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_arg
和
another_arg
并且您的实现能够处理任何事情,那么这是允许的。但一般来说,您会希望您的协议能够指导您实际想要采取的措施。)