我有一个父类
BaseBlob
,它有几个子类LimitedProofId
,ProofId
,TxId
。父类实现了一个 deserialize
类方法,该方法应该返回其自身的实例。
我还有一个
Delegation
课程需要LimitedProofId
。如果我错误地传递了 BaseBlob
的另一个子实例(例如 ProofId
或 TxId
),我特别希望 mypy出错。
from __future__ import annotations
from io import BytesIO
class BaseBlob:
def __init__(self, data: bytes):
self.data = data
@classmethod
def deserialize(cls, stream: BytesIO) -> BaseBlob:
return cls(stream.read(32))
class LimitedProofId(BaseBlob):
pass
class TxId(BaseBlob):
pass
class Delegation:
def __init__(self, ltd_id: LimitedProofId):
self.ltd_id = ltd_id
def deserialize(self, stream: BytesIO) -> Delegation:
ltd_id = LimitedProofId.deserialize(stream)
return Delegation(ltd_id)
mypy 显示此代码的错误,因为 if 认为
LimitedProofId.deserialize
返回 BaseBlob
。
错误:“Delegation”的参数 1 具有不兼容的类型“BaseBlob”;预期“LimitedProofId”[arg-type]
我已经看到类似问题的答案,使用
T = TypeVar('T', bound='BaseBlob')
来实现允许子类的类型注释,但如果我这样做,我需要为 T
的返回类型和第一个参数指定 BaseBlob.deserialize
Delegation.__init__
,这违背了我对后者类型安全的目的。
有没有办法实现我想做的事情,而不必在所有子类上重新实现
deserialize
?
您想要表达
deserialize
返回它所绑定的类的实例。
>=3.9, <3.11
...
from typing import TypeVar
T = TypeVar("T", bound="BaseBlob")
class BaseBlob:
...
@classmethod
def deserialize(cls: type[T], stream: BytesIO) -> T:
return cls(stream.read(32))
这些更改使您上面发布的代码完全类型安全,并且
mypy --strict
同意。
从像
LimitedProofId.deserialize
这样的子类调用会绑定该方法,因此 cls
将是 LimitedProofId
,从打字的角度来看,它又相应地在 T
中绑定 type[T]
。
<3.9
与上面类似,但从 Type
导入
typing
并将 type[T]
注释替换为 Type[T]
。
>=3.11
@chepner 所说的。应该很快就能与
mypy
合作。
我不明白你的意思:(突出显示)
我已经看到类似问题的答案,这些问题使用
来实现允许子类的类型注释,但如果我这样做,我需要为T = TypeVar('T', bound='BaseBlob')
T
的返回类型和第一个参数指定BaseBlob.deserialize
,这违背了我对后者类型安全的目的。Delegation.__init__
__init__
与此有什么关系?
如果我没能理解你的意图,请详细说明,我会尽力调整答案。
由于您似乎不确定这些注释如何影响类型检查器推断相关类型的方式,因此这里有一个完整的演示:
from __future__ import annotations
from io import BytesIO
from typing import TypeVar
T = TypeVar("T", bound="BaseBlob")
class BaseBlob:
def __init__(self, data: bytes):
self.data = data
@classmethod
def deserialize(cls: type[T], stream: BytesIO) -> T:
return cls(stream.read(32))
class LimitedProofId(BaseBlob):
pass
class TxId(BaseBlob):
pass
class Delegation:
def __init__(self, ltd_id: LimitedProofId):
self.ltd_id = ltd_id
@classmethod
def deserialize(cls, stream: BytesIO) -> Delegation:
ltd_id = LimitedProofId.deserialize(stream)
return cls(ltd_id)
if __name__ == "__main__":
ltd = LimitedProofId(b"xyz")
d = Delegation.deserialize(BytesIO(b"abc"))
reveal_type(d.ltd_id)
reveal_type(LimitedProofId.deserialize(BytesIO()))
reveal_type(TxId.deserialize(BytesIO()))
mypy --strict
输出没有错误:
注意:显示的类型是“LimitedProofId” 注意:显示的类型是“LimitedProofId” 注意:显示的类型是“TxId” 成功:1 个源文件中未发现问题
Python 3.11 为此引入了
Self
类型提示。 (PEP 673 更详细地描述了代码 Self
旨在简化,如果您尚未升级到 3.11。)
from typing import Self
class BaseBlob:
def __init__(self, data: bytes):
self.data = data
@classmethod
def deserialize(cls, stream: BytesIO) -> Self:
return cls(stream.read(32))