Search code examples
pythonpython-typingmypyderived-class

mypy complains about extended base class' attribute type


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:

  1. Is this a violation of Liskov principle?
  2. 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?
  3. 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).


Solution

  • 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.