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

How to avoid circular imports in Python?

本问题已经有最佳答案,请猛点这里访问。

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

有人能告诉我在这种情况下如何避免循环导入吗?:我有两个类,我希望每个类都有一个构造函数(方法),它获取另一个类的实例并返回该类的实例。

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

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

一个玩具的例子是类A,它有一个属性是列表,类B,它有一个属性是元组。然后,类A有一个方法,该方法获取类B的实例并返回类A的实例(通过将元组转换为列表),同样,类B也有一个方法,该方法获取类A的实例并返回类B的实例(通过将列表转换为元组)。


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

1
2
3
4
/package
    __init__.py
    a.py
    b.py

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

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

不幸的是,只有第1和第4个选项在具有循环依赖关系时才有效(其余选项都会提升ImportErrorAttributeError)。一般来说,您不应该使用第四种语法,因为它只在python2中工作,并且可能与其他第三方模块发生冲突。所以实际上,只有第一种语法是可以保证工作的。但是,在处理循环依赖关系时,仍然有几个选项。

EDIT: The ImportError and AttributeError issues only occur in
python 2. In python 3 the import machinery has been rewritten and all
of these import statements (with the exception of 4) will work, even with
circular dependencies.

使用绝对导入

只需使用上面的第一个导入语法。这种方法的缺点是,对于大型包,导入名称可能会变得过长。

a.py

1
import package.b

b.py

1
import package.a

将导入推迟到稍后

我在很多软件包中都看到过这种方法,但我仍然觉得它很糟糕,我不喜欢看不到模块的顶部并看到它的所有依赖项,我还必须搜索所有的函数。

a.py

1
2
def func():
    from package import b

b.py

1
2
def func():
    from package import a

将所有导入内容放入中央模块

这也可以,但与第一个方法有相同的问题,在这个方法中,所有的包和子模块调用都是超长的。它还存在两个主要缺陷——它强制导入所有子模块,即使您只使用一个或两个,并且您仍然无法查看任何子模块,并且很快在顶部看到它们的依赖关系,您必须筛选函数。

__init__.py

1
2
from . import a
from . import b

a.py

1
2
3
4
import package

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

b.py

1
2
3
4
import package

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

所以这些是你的选择(在我看来都很糟糕)。坦率地说,这似乎是python导入机器中的一个明显缺陷,但这只是我的观点。


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

a.py为例:

1
2
3
4
5
import b

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

b.py

1
2
3
4
5
import a

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

这个很好用。


我们结合了绝对导入和函数,以便更好地读取和缩短访问字符串。

  • 优点:与纯绝对导入相比,访问字符串更短
  • 缺点:额外的函数调用会增加一些开销

主/子/ A/Py

1
2
3
4
5
6
import main.sub.b
b_mod = lambda: main.sub.b

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

主/副/BY

1
2
3
4
5
6
import main.sub.a
a_mod = lambda: main.sub.a

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