I have a parent class BaseBlob
that has several child classes LimitedProofId
, ProofId
, TxId
. The parent class implement a deserialize
class method that should return an instance of itself.
I also have a Delegation
class that takes a LimitedProofId
. I specifically want mypy to error if I pass by mistake another child instance of BaseBlob
such as a ProofId
or a TxId
.
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 shows an error for this code, because if thinks LimitedProofId.deserialize
returns a BaseBlob
.
error: Argument 1 to "Delegation" has incompatible type "BaseBlob"; expected "LimitedProofId" [arg-type]
I have seen answer to similar questions that use a T = TypeVar('T', bound='BaseBlob')
to achieve a type annotation that allows child classes, but if I do that I need to specify T
both for the return type of BaseBlob.deserialize
and the first parameter of Delegation.__init__
, which defeat my purpose of type safety for the latter.
Is there a way to achieve what I want to do, without having to reimplement deserialize
on all child classes?
CodePudding user response:
Python 3.11 introduces the Self
type hint for this. (PEP 673 describes in more detail the code Self
is intended to simplify, if you haven't upgraded to 3.11 yet.)
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))
CodePudding user response:
You want to express that deserialize
returns an instance of the class that it is bound to.
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))
These changes make the code you posted above perfectly type safe and mypy --strict
agrees.
Calling from a child class like LimitedProofId.deserialize
binds the method, so that cls
will be LimitedProofId
, which from a typing perspective in turn binds T
in type[T]
accordingly.
Python <3.9
Like the above, but import Type
from typing
and replace the type[T]
annotation with Type[T]
.
Python >=3.11
What @chepner said. Should work soon with mypy
.
Clarification
I don't understand, what you mean: (highlighted)
I have seen answer to similar questions that use a
T = TypeVar('T', bound='BaseBlob')
to achieve a type annotation that allows child classes, but if I do that I need to specifyT
both for the return type ofBaseBlob.deserialize
and the first parameter ofDelegation.__init__
, which defeat my purpose of type safety for the latter.
What does __init__
have to do with this?
If I failed to understand your intention, please elaborate and I will do my best to adjust the answer.