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