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:

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

Asked By: PVitt

||

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.

Answered By: PVitt

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.

Answered By: Andrew Sherry