There is an approach that combines a __future__
import to disregard type annotations at runtime, with a if TYPE_CHECKING
clause that "imports" the code from your IDE's point of view only, so that code completion is available.
Example:
my_number.py
class MyNumber:
def __init__(self):
self.x = 5
from my_method import my_method
my_method.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from my_number import MyNumber
def my_method(self: MyNumber):
print(self.x)
With the from __future__ import annotations
, we postpone the evaluation of type hints - in other words, we can type hint my_method
even if we don't actually import MyNumber
. This behavior was planned to be the default in Python 3.10, but it got postponed, so we need this import for now.
Now depending on your editor/IDE, you will still get a warning complaining that MyNumber
isn't defined, and its methods/attributes may not show up on the autocomplete. Here's where the TYPE_CHECKING
comes into play: this is simply a constant False
value, which means that our clause is:
if False:
from my_number import MyNumber
In other words, we're "tricking" the IDE into thinking we're importing MyNumber
, but in reality that line never executes. Thus we avoid the circular import altogether.
This might feel a little hacky, but it works :-) the whole point of the TYPE_CHECKING
constant is to allow type checkers to do their job, while not actually importing code at runtime, and to do so in a clear way (Zen of Python: There should be one-- and preferably only one --obvious way to do it).
This approach has worked for me consistently in PyCharm, not sure about other IDEs/editors.