Incompatible Type: __main__.TestClass; expected example.TestClass; Passing 'self' to function in another module

Viewed 82

Background

I have two modules. Module 1 (example) defines TestClass. Module 2 (example2) defines functions change_var which takes an argument TestClass. example has a method change which calls change_var from example2 and passes self as argument.

example2 uses TYPE_CHECKING from typing to ensure cyclic import does not appear at run-time, but still allows MYPY to check types.

At the call to change_var from within change, MYPY gives the error Argument 1 to "change_var" has incompatible type "__main__.TestClass"; expected "example.TestClass".

Python Version: 3.7.3, MYPY Version: 0.701

Example Code

example.py

from example2 import change_var


class TestClass:
    def __init__(self) -> None:
        self.test_var = 1

    def change(self) -> None:
        change_var(self)

example2.py

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from example import TestClass


def change_var(obj: "TestClass") -> None:
    obj.test_var = 2

This code is a minimal example of the actual problem I am experiencing in a larger python project.

What I Expect this to do

These types should match as they are (as far as I can tell) the same.

My intuition as to why this doesn't work is that TestClass, at the point of calling to change_var isn't fully defined? For the same reason I can't refer to TestClass as a type within TestClass itself, I can't pass a TestClass object to a function that expects a TestClass object from withing the class itself. To MYPY, this is not a full class yet so it uses some kind of placeholder type. This is only an intuition though.

Questions

  1. What exactly is the problem here?
  2. What is the best work around to achieve this general code structure (two modules, class in one, function that takes class in other, method calls to function) while still making MYPY happy?

I am also open to refactoring this example entirely but I'd like to try to stick to this general structure.

1 Answers

This is one of many cases of breakage from treating a module as a script (whether via -m, -c (or --command for mypy), or simply python …/module.py). It works only for trivial applications that do not care about the identity of the types or functions they create. (They must also avoid side effects on import and mutable global state, but those are good ideas anyway.)

The solution, beyond “don’t do that”, is to use __main__.py in a package. Even that is not without issues, since certain naïve recursive importers will import it as if it were a true module.

Related