Python type hinting without cyclic imports Python type hinting without cyclic imports python python

Python type hinting without cyclic imports


There isn't a hugely elegant way to handle import cycles in general, I'm afraid. Your choices are to either redesign your code to remove the cyclic dependency, or if it isn't feasible, do something like this:

# some_file.pyfrom typing import TYPE_CHECKINGif TYPE_CHECKING:    from main import Mainclass MyObject(object):    def func2(self, some_param: 'Main'):        ...

The TYPE_CHECKING constant is always False at runtime, so the import won't be evaluated, but mypy (and other type-checking tools) will evaluate the contents of that block.

We also need to make the Main type annotation into a string, effectively forward declaring it since the Main symbol isn't available at runtime.

If you are using Python 3.7+, we can at least skip having to provide an explicit string annotation by taking advantage of PEP 563:

# some_file.pyfrom __future__ import annotationsfrom typing import TYPE_CHECKINGif TYPE_CHECKING:    from main import Mainclass MyObject(object):    # Hooray, cleaner annotations!    def func2(self, some_param: Main):        ...

The from __future__ import annotations import will make all type hints be strings and skip evaluating them. This can help make our code here mildly more ergonomic.

All that said, using mixins with mypy will likely require a bit more structure then you currently have. Mypy recommends an approach that's basically what deceze is describing -- to create an ABC that both your Main and MyMixin classes inherit. I wouldn't be surprised if you ended up needing to do something similar in order to make Pycharm's checker happy.


For people struggling with cyclic imports when importing class only for Type checking: you will likely want to use a Forward Reference (PEP 484 - Type Hints):

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

So instead of:

class Tree:    def __init__(self, left: Tree, right: Tree):        self.left = left        self.right = right

you do:

class Tree:    def __init__(self, left: 'Tree', right: 'Tree'):        self.left = left        self.right = right


The bigger issue is that your types aren't sane to begin with. MyMixin makes a hardcoded assumption that it will be mixed into Main, whereas it could be mixed into any number of other classes, in which case it would probably break. If your mixin is hardcoded to be mixed into one specific class, you may as well write the methods directly into that class instead of separating them out.

To properly do this with sane typing, MyMixin should be coded against an interface, or abstract class in Python parlance:

import abcclass MixinDependencyInterface(abc.ABC):    @abc.abstractmethod    def foo(self):        passclass MyMixin:    def func2(self: MixinDependencyInterface, xxx):        self.foo()  # ← mixin only depends on the interfaceclass Main(MixinDependencyInterface, MyMixin):    def foo(self):        print('bar')