如何注释父类方法,以便子方法返回自身的实例?

问题描述 投票:0回答:2

我有一个父类

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

python inheritance python-typing mypy
2个回答
2
投票

您想要表达

deserialize
返回它所绑定的类的实例。

Python
>=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]


Python
<3.9

与上面类似,但从 Type

 导入 
typing
 并将 
type[T]
注释替换为
Type[T]


Python
>=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 个源文件中未发现问题

2
投票

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))
© www.soinside.com 2019 - 2024. All rights reserved.