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

the*_*ura 3 python annotations circular-dependency python-3.x python-typing

当我有以下最小重现代码时:

启动.py

from __future__ import annotations

import a
Run Code Online (Sandbox Code Playgroud)

a.py

from __future__ import annotations

from typing import Text

import b

Foo = Text
Run Code Online (Sandbox Code Playgroud)

b.py

from __future__ import annotations

import a

FooType = a.Foo
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

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)
Run Code Online (Sandbox Code Playgroud)

我之所以将其包含在内,__future__.annotations是因为大多数此类质量保证都是通过简单地将未来的导入包含在文件顶部来解决的。但是,注释导入并不能改善这里的情况,因为简单地将类型转换为文本(如注释导入所做的那样)实际上并不能解决导入顺序依赖性。

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

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)
Run Code Online (Sandbox Code Playgroud)

有哪些可用选项可以解决此问题?是否有其他方法可以“提升”类型注释,以便在导入所有内容后对它们进行评估?

STe*_*kov 7

在大多数情况下,使用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
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
# __main__.py
from a import A
from b import B
Run Code Online (Sandbox Code Playgroud)

然而,对于你的 MRE 来说,它是行不通的。如果循环依赖不仅是由类型注释(例如类型别名)引入的,那么解决可能会变得非常棘手。

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