假设我有一个实现方法链的类:
from __future__ import annotations
class M:
def set_width(self, width: int)->M:
self.width = width
return self
def set_height(self, height: int)->M:
self.height = height
return self
我可以这样使用它:
box = M().set_width(5).set_height(10)
这可行,但如果我有一个子类 M3D:
class M3D(M):
def set_depth(self, depth: int) -> M3D:
self.depth = depth
return self
现在我不能这样做:
cube = M3D().set_width(2).set_height(3).set_depth(5)
我在 mypy 中收到以下错误:
_test_typeanotations.py:21: error: "M" has no attribute "set_depth"; maybe "set_width"
因为
set_width()
返回一个没有方法 M
的 set_depth
。我已经看到建议为每个子类重写 set_width()
和 set_height()
以指定正确的类型,但这将为每个方法编写大量代码。必须有一个更简单的方法。
这也与特殊方法相关,例如
__enter__
传统上返回 self
,因此最好有一种方法来指定它,甚至不需要在子类中提及它。
在 Python 3.11 及更高版本中,您可以执行以下操作:
from typing import Self
class M:
def set_width(self, width: int) -> Self:
self.width = width
return self
经过大量研究和实验,我找到了一种在 mypy 中有效的方法,尽管 Pycham 有时仍然会猜测类型错误。
诀窍是使
self
成为类型 var:
from __future__ import annotations
import asyncio
from typing import TypeVar
T = TypeVar('T')
class M:
def set_width(self: T, width: int)->T:
self.width = width
return self
def set_height(self: T, height: int)->T:
self.height = height
return self
def copy(self)->M:
return M().set_width(self.width).set_height(self.height)
class M3D(M):
def set_depth(self: T, depth: int) -> T:
self.depth = depth
return self
box = M().set_width(5).set_height(10) # box has correct type
cube = M3D().set_width(2).set_height(3).set_depth(5) # cube has correct type
attemptToTreatBoxAsCube = M3D().copy().set_depth(4) # Mypy gets angry as expected
最后一行在 mypy 中工作得很好,但 pycharm 有时仍会自动完成
set_depth
,即使 .copy()
实际上返回 M
,即使在 M3D
上调用时也是如此。
这是任何使用继承的语言中的一个经典问题。不同语言的处理方式也不同:
set_height
之前转换
set_depth
Python 是一种动态类型语言,因此没有强制类型转换指令。所以你有 3 种可能的方法:
我不会仅仅为了消除警告而重写方法。毕竟 Python 是一种动态类型语言,甚至允许鸭子类型。如果我知道代码是正确的,我会避免添加无用的代码(DRY 和 KISS 原则)
所以我只是假设暂停注释控件的注释是出于某种原因而发明的并使用它们(我称之为不要在这里打扰我
)。