令我惊讶的是,还没有人提到由类型提示引起的循环导入。
如果由于类型提示而仅有循环导入,则可以以干净的方式避免它们。
考虑main.py,它利用了另一个文件中的异常:
from src.exceptions import SpecificException
class Foo:
def __init__(self, attrib: int):
self.attrib = attrib
raise SpecificException(Foo(5))
还有专用的异常类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}.'
这将通过 main.py 导入 exception.py 并通过 Foo 和 SpecificException 轻松提升 ImportError。
因为Foo 在类型检查期间仅在exceptions.py 中是必需的,所以我们可以使用typing 模块中的TYPE_CHECKING 常量安全地使其有条件导入。类型检查时常量只有True,这样我们可以有条件地导入Foo,从而避免循环导入错误。
在 Python 3.6 中,使用前向引用:
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}.'
在 Python 3.7+ 中,延迟评估注释(在 PEP 563 中引入)允许使用“普通”类型而不是前向引用:
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}.'
在 Python 3.11+ 中,from __future__ import annotations 默认处于活动状态,因此可以省略。
此答案基于 Stefaan Lippens 的 Yet another solution to dig you out of a circular import hole in Python。