mypy 如何将 Liskov 替换原则应用于
*args, **kwargs
参数?
我认为以下代码应该无法通过 mypy 检查,因为
f
类允许对 Base
的某些调用不被 C
允许,但它实际上通过了。这有什么原因吗?
from abc import ABC, abstractmethod
from typing import Any
class Base(ABC):
@abstractmethod
def f(self, *args: Any, **kwargs: Any) -> int:
pass
class C(Base):
def f(self, batch: int, train: bool) -> int:
return 1
我也尝试删除
*args
或**kwargs
,但都失败了。
与目前接受的答案中所说的Daniil不同,原因正是
(*args: Any, **kwargs: Any)
签名部分。
请查看mypy
问题跟踪器上的相应讨论:
我实际上很喜欢这个想法,我已经多次看到这种混乱,虽然它有点不安全,但大多数时候当人们写 (*args, **kwargs) 时,它意味着“不关心”,而不是“应该适用于所有呼叫”。
[GVR] 同意,这是实用性胜过纯粹性的情况。
所以,
mypy
对形式的功能进行了特殊处理
# _T is arbitrary type
class _:
def _(self, *args, **kwargs) -> _T: ...
并认为它们完全等同于
Callable[..., _T]
。
是的,当然,这实际上违反了 LSP,但这是专门设计的,允许使用签名“忽略我的参数”来声明函数。
要声明真正接受任意位置和关键字参数的最广泛的函数,您应该在签名中使用
object
。
这与
*args
或 **kwargs
本身无关。其原因完全是因为您对两个注释都使用了 typing.Any
。
Any
注释基本上是类型检查器的绝地思维技巧,其效果是:
这些是您正在寻找的类型。
不管怎样,总会过去的。
因此,
typing
文档特别建议尽可能多地使用object
来代替Any
,当你想说“最广泛的可能类型”之类的东西时。当您遇到 Python 类型系统的限制时,Any
应保留为最后的手段。
mypy
文档还有一个部分解释了
Any
和
object
之间的区别。如果您将其中一个
Any
注释更改为
object
,您将受到
mypy
的正确惩罚,并出现
[override]
的
C.f
错误。示例:
from typing import Any
class Base:
def f(self, *args: object, **kwargs: Any) -> int:
return 2
class C(Base):
def f(self, batch: int, train: bool) -> int: # now this is an error
return 1
然而,“任意数量的位置和关键字参数”与“每个参数将始终通过类型检查”的组合本质上翻译为“”没有覆盖永远是错误的“(就参数而言) ).
所以我建议使用 object
Any
everywhere
,除非你无法避免使用后者。这些混乱是我认为选择命名这个构造的原因之一
Any
非常不幸。
PS 我的第一段措辞不好。正如*args
/**kwargs
参数的 combination
以及它们用
Any
注释。只有当两个条件都满足时,
mypy
才会例外。