在 __future__.annotations 不足的情况下避免使用类型注释进行循环导入

Avoiding circular imports with type annotations in situations where __future__.annotations is insufficient

当我有以下最小复制代码时:

start.py

from __future__ import annotations

import a

a.py

from __future__ import annotations

from typing import Text

import b

Foo = Text

b.py

from __future__ import annotations

import a

FooType = a.Foo

我收到以下错误:

soot@soot:~/code/soot/experimental/amol/typeddict-circular-import$ python3 start.py
Traceback (most recent call last):
  File "start.py", line 3, in <module>
    import a
  File "/home/soot/code/soot/experimental/amol/typeddict-circular-import/a.py", line 5, in <module>
    import b
  File "/home/soot/code/soot/experimental/amol/typeddict-circular-import/b.py", line 5, in <module>
    FooType = a.Foo
AttributeError: partially initialized module 'a' has no attribute 'Foo' (most likely due to a circular import)

我包含了 __future__.annotations 因为大多数此类问题通过简单地在文件顶部包含将来的导入来解决。但是,注释导入并没有改善这里的情况,因为简单地将类型转换为文本(就像注释导入所做的那样)实际上并没有解决导入顺序依赖性。

更广泛地说,每当您想从多个(可能是循环的)来源创建复合类型时,这似乎都是一个问题,例如

CompositeType = Union[a.Foo, b.Bar, c.Baz]

解决此问题的可用选项有哪些?有没有其他方法可以 'lift' 类型注释,以便在导入所有内容后对它们进行评估?

在大多数情况下,使用 typing.TYPE_CHECKING 有助于解决循环导入问题。

# a.py
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from b import B

class A: pass
def foo(b: B) -> None: pass
# b.py
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from a import A

class B: pass
def bar(a: A) -> None: pass
# __main__.py
from a import A
from b import B

但是,对于您的 MRE,它是行不通的。如果循环依赖不仅仅通过类型注释(例如你的类型别名)引入,解析可能会变得非常棘手。

如果您的示例在运行时不需要 Foo,它也可以在 if TYPE_CHECKING: 块中声明,mypy 将正确解释它。如果它也适用于运行时,那么一切都取决于确切的代码结构(在你的 MRE 中删除 import b 就足够了)。联合类型可以在单独的文件中声明,该文件导入 abc 并创建联合。如果您需要 abc 中的联合,那么事情会稍微复杂一些,可能需要将某些功能提取到创建联合的单独文件 d 中并使用它(这样代码也会更清晰一些,因为每个文件都只包含通用功能)。