mypy complains about extended base class' attribute type
Question:
I have two base classes A
and B
that are defined like this:
class A(object):
def common_function(self):
pass
class B(object):
def __init__(self, a: A):
self.a = a
def another_common_function(self):
pass
Class A
holds some management information, whereas class B
holds some other information, which is based on the information contained in class A
, and therefore it knows about it’s instance of A
.
I also have two derived classes dA
and dB
that are defined like this:
class dA(A):
def __init__(self, t: B):
self.t = t
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
These classes (among others (dA1
, dB1
, dA2
, dB2
) … that are similarly designed) are used for some special operation and therefore they need to store some more information, e.g. the t from the example for this pair of classes, other classes have different stuff to store.
The problem is, that mypy complains about the usage of dB.a.t
:
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
def do(self):
if self.a.t is None:
print("something")
test.py: error: “A” has no attribute “t”
The complain is actually right. A
doesn’t have an attribute t
. I also told mypy that B.a
is of type A
, but in this particular case I use dB.a
as of type dA
, which actually has a t, but I explicitly told mypy otherwise.
The questions are:
- Is this a violation of Liskov principle?
- If not 1, is there a way to tell mypy that in this particular case
dB.a
is of type dA
? Do I need to use a TypeVar?
- If 1, is there a way to restructure the classes to not violate the Liskov principle as well as have the type checker be able to recognize the correct types?
I found the question mypy: base class has no attribute x, how to type hint in base class, however, the solution to extend the base class is not feasible, as this would make t
available in all derived classes, not only dA
(what somehow smells bad).
Answers:
It is possible to ensure that self.a
is of type dA
by using assert:
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
def do(self):
assert isinstance(self.a, dA)
if self.a.t is None:
print("something")
This assert is recognized by mypy, so that self.a
is known as instance of dA
afterwards and thus has an attribute t
.
You are able to specify the type of self.a
in this instance by doing the following;
class dB(B):
a: dA
... # Rest as before
It should be noted that I was not able to replicate the code provided by OP, as mypy
was complaining that the second argument to super
in dB
was not of the same instance as argument 1. None the less, this should work for you as it did me in a situation similar to this.
I have two base classes A
and B
that are defined like this:
class A(object):
def common_function(self):
pass
class B(object):
def __init__(self, a: A):
self.a = a
def another_common_function(self):
pass
Class A
holds some management information, whereas class B
holds some other information, which is based on the information contained in class A
, and therefore it knows about it’s instance of A
.
I also have two derived classes dA
and dB
that are defined like this:
class dA(A):
def __init__(self, t: B):
self.t = t
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
These classes (among others (dA1
, dB1
, dA2
, dB2
) … that are similarly designed) are used for some special operation and therefore they need to store some more information, e.g. the t from the example for this pair of classes, other classes have different stuff to store.
The problem is, that mypy complains about the usage of dB.a.t
:
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
def do(self):
if self.a.t is None:
print("something")
test.py: error: “A” has no attribute “t”
The complain is actually right. A
doesn’t have an attribute t
. I also told mypy that B.a
is of type A
, but in this particular case I use dB.a
as of type dA
, which actually has a t, but I explicitly told mypy otherwise.
The questions are:
- Is this a violation of Liskov principle?
- If not 1, is there a way to tell mypy that in this particular case
dB.a
is of typedA
? Do I need to use a TypeVar? - If 1, is there a way to restructure the classes to not violate the Liskov principle as well as have the type checker be able to recognize the correct types?
I found the question mypy: base class has no attribute x, how to type hint in base class, however, the solution to extend the base class is not feasible, as this would make t
available in all derived classes, not only dA
(what somehow smells bad).
It is possible to ensure that self.a
is of type dA
by using assert:
class dB(B):
def __init__(self, a: dA):
super(A, self).__init__(a)
def do(self):
assert isinstance(self.a, dA)
if self.a.t is None:
print("something")
This assert is recognized by mypy, so that self.a
is known as instance of dA
afterwards and thus has an attribute t
.
You are able to specify the type of self.a
in this instance by doing the following;
class dB(B):
a: dA
... # Rest as before
It should be noted that I was not able to replicate the code provided by OP, as mypy
was complaining that the second argument to super
in dB
was not of the same instance as argument 1. None the less, this should work for you as it did me in a situation similar to this.