在 __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
就足够了)。联合类型可以在单独的文件中声明,该文件导入 a
、b
和 c
并创建联合。如果您需要 a
、b
或 c
中的联合,那么事情会稍微复杂一些,可能需要将某些功能提取到创建联合的单独文件 d
中并使用它(这样代码也会更清晰一些,因为每个文件都只包含通用功能)。
当我有以下最小复制代码时:
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
就足够了)。联合类型可以在单独的文件中声明,该文件导入 a
、b
和 c
并创建联合。如果您需要 a
、b
或 c
中的联合,那么事情会稍微复杂一些,可能需要将某些功能提取到创建联合的单独文件 d
中并使用它(这样代码也会更清晰一些,因为每个文件都只包含通用功能)。