To my surprise, no one has mentioned cyclic imports caused by type hints yet.
If you have cyclic imports only as a result of type hinting, they can be avoided in a clean manner.
Consider main.py which makes use of exceptions from another file:
from src.exceptions import SpecificException
class Foo:
def __init__(self, attrib: int):
self.attrib = attrib
raise SpecificException(Foo(5))
And the dedicated exception class exceptions.py:
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo):
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
This will trivially raise an ImportError as main.py imports exception.py and vice versa through Foo and SpecificException.
Because Foo is only required in exceptions.py during type checking, we can safely make its import conditional using the TYPE_CHECKING constant from the typing module. The constant is only True during type checking, which allows us to conditionally import Foo and thereby avoid the circular import error.
In Python 3.6, with the use of forward references:
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: 'Foo'): # The quotes make Foo a forward reference
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
In Python 3.7+, postponed evaluation of annotations (introduced in PEP 563) allows 'normal' types to be used instead of forward references:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo): # Foo can be used in type hints without issue
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
In Python 3.11+, from __future__ import annotations is active by default and can therefore be omitted.
This answer is based on Yet another solution to dig you out of a circular import hole in Python by Stefaan Lippens.