如何在 Python 中避免循环导入?

我知道在 python 中循环导入的问题以前出现过很多次,我已经阅读了这些讨论。在这些讨论中反复提出的意见是,循环导入是糟糕设计的标志,应该重新组织代码以避免循环导入。

谁能告诉我在这种情况下如何避免循环进口?我有两个类,我希望每个类都有一个构造函数(方法) ,它接受另一个类的一个实例并返回该类的一个实例。

更具体地说,一个类是可变的,一个是不可变的。不可变的类是必需的 用于散列、比较等。可变类也需要执行这些操作。这类似于集合和冻结集或列表和元组。

我可以将两个类定义放在同一个模块中。还有其他建议吗?

一个玩具的例子是类 A,它有一个属性是一个列表,类 B,它有一个属性是一个元组。然后类 A 有一个方法,它接受类 B 的一个实例并返回类 A 的一个实例(通过将元组转换为一个列表) ,类似地,类 B 有一个方法,它接受类 A 的一个实例并返回类 B 的一个实例(通过将列表转换为一个元组)。

142234 次浏览

只导入模块,不要从模块导入:

考虑 a.py:

import b


class A:
def bar(self):
return b.B()

b.py:

import a


class B:
def bar(self):
return a.A()

这个完全没问题。

考虑下面的示例 python 包,其中 a.pyb.py相互依赖:

/package
__init__.py
a.py
b.py

循环导入问题的类型

循环导入依赖项通常分为两类,具体取决于 你想要导入什么以及你在哪里使用它 模块(以及是否使用 python2或3)。

1. 导入具有循环导入的模块时出错

在某些情况下,仅 进口是一个具有循环导入依赖关系的模块 可能导致错误,即使您没有从 进口模块。

在 python 中导入模块有几种标准方法

import package.a           # (1) Absolute import
import package.a as a_mod  # (2) Absolute import bound to different name
from package import a      # (3) Alternate absolute import
import a                   # (4) Implicit relative import (deprecated, python 2 only)
from . import a            # (5) Explicit relative import

不幸的是,只有第一个和第四个选项在您 有循环依赖关系(其余的都提高 ImportError 在一般情况下,你不应该使用 第4种语法,因为它只能在 python2中工作,并且运行 与其他第三方模块冲突。所以,真的,只有第一 语法保证可以工作。

编辑: ImportErrorAttributeError问题仅发生在 Python2。在 python3中,导入机器已经被重写 这些导入语句(除了4)将工作,甚至与 尽管本节中的解决方案可能有助于重构 python3代码,但它们主要是为了 对于使用 python 2的人来说。

绝对进口

只需使用上面的第一个导入语法 导入名称可以得到大包的 超长

a.py

import package.b

b.py

import package.a

将导入推迟到以后

我已经在很多软件包中看到过这种方法,但是它仍然让人感觉 我不喜欢我不能看模块的顶部 看到它的所有依赖项,我必须搜索所有 也能起作用。

a.py

def func():
from package import b

b.py

def func():
from package import a

将所有导入放在一个中央模块中

这也可以工作,但是与第一个方法有相同的问题,其中 所有的包和子模块调用都得到 超长 主要缺陷——它强制导入 所有子模块,即使 你只用了一两个,但是你仍然不能看到任何 子模块,并在顶部快速查看它们的依赖关系,您必须 对函数进行筛选。

__init__.py

from . import a
from . import b

a.py

import package


def func():
package.b.some_object()

b.py

import package


def func():
package.a.some_object()

使用具有循环依赖关系的导入对象时出错

现在,虽然您可以使用循环导入来导入 模组 依赖项,则无法导入模块中定义的任何对象 或者实际上能够在任何地方引用导入的模块 在你导入它的模块的顶层,你可以, 但是,使用导入的模块 在里面函数和代码块不 在进口货物上运行。

例如,这将工作:

包裹/A.py

import package.b


def func_a():
return "a"

Package/b.py

import package.a


def func_b():
# Notice how package.a is only referenced *inside* a function
# and not the top level of the module.
return package.a.func_a() + "b"

但这样不行

包裹/A.py

import package.b


class A(object):
pass

Package/b.py

import package.a


# package.a is referenced at the top level of the module
class B(package.a.A):
pass

你会得到一个例外

AttributeError: 模块‘ package’没有属性‘ a’

通常,在循环依赖的大多数有效情况下,这是可能的 重构或重新组织代码以防止这些错误并移动 代码块内的模块引用。

我们结合使用绝对导入和函数来更好地读取和更短的访问字符串。

  • 优点: 与纯绝对导入相比,访问字符串更短
  • 缺点: 由于额外的函数调用,开销有点大

Main/sub/a.py

import main.sub.b
b_mod = lambda: main.sub.b


class A():
def __init__(self):
print('in class "A":', b_mod().B.__name__)

Main/sub/b.py

import main.sub.a
a_mod = lambda: main.sub.a


class B():
def __init__(self):
print('in class "B":', a_mod().A.__name__)