Below is the minimal example of my problem:
[test/__init__.py]
from test.test1 import Test1
from test.test2 import Test2
[test/test1.py]
class Test1:
pass
[test/test2.py]
from test import Test1
class Test2:
pass
Mypy output for module or __init__.py:
test/test2.py:1: error: Module 'test' has no attribute 'Test1'
Code itself works well both on Python 2 and Python 3.
This is because your code has an import cycle -- in order for mypy to parse test/__init__.py
, it needs to understand exactly what Test2
is. (After all, what if you decide to use Test2
later on in that file/call one of its methods? Then mypy needs to know what the output is).
So, it hits that import, effectively pauses, and jumps to trying to understand what test/test2.py
is doing*.
But within test/test2.py
, we run into the exact same problem -- we see the import, and need to jump back to test/__init__.py
in order to understand what Test1
is... But we haven't finished parsing that file!
This is where mypy differs from the Python runtime, fyi -- mypy can only parse entire files at a time, but the Python runtime will actually pause execution to run test/test2.py
. That means when you do from test import Test1
, the test
module has a partially complete symbol space that happens to currently contain only Test1
, instead of both Test1
and Test2
, which is why your code works at runtime.
The fix, in this case, would be to modify the import in test/test2.py
to:
from test.test1 import Test1
This breaks the import cycle.
*This isn't actually what mypy does -- what it actually does is attempt to resolve import cycles by first identifying all strongly connected components (SCC) -- each SCC is basically an import cycle.
It then applies some heuristics to determine the order in which the files within the SCC should be processed, but it's an imperfect process and can't resolve all import cycles.
For example, in your case, whether we process test
or test.test2
first, we're going to run into issues.
You can see the SCCs mypy identifies by re-running mypy using the -v
flag (for verbose mode).
You can find more details about the algorithm mypy uses within the source code here. Specific details about the import cycle resolution algorithm can be found a little lower down, here.
(I suspect this entire comment is slightly out-of-date/incomplete, fyi -- there are several other wrinkles related to mypy's silent import mechanism and its incremental mode mechanism that aren't really explained.)
Info about the exact heuristics mypy uses to order SCCs can be found here. (Basically, the exact syntax we use for importing something can give some hints about how that SCC should be ordered).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With