考虑以下示例Python包,其中a.py
和b.py
彼此依赖:
/package
__init__.py
a.py
b.py
循环导入问题的类型
循环导入依赖通常分为两类,取决于您正在尝试导入什么以及在每个模块中使用它的位置。 (以及您是否正在使用Python 2还是3)。
1. 导入具有循环依赖性的模块时出现错误
在某些情况下,即使您没有引用导入的模块中的任何内容,仅仅导入一个具有循环导入依赖关系的模块也可能会导致错误。
Python有几种标准的导入模块方式。
import package.a
import package.a as a_mod
from package import a
import a
from . import a
很遗憾,只有第一种和第四种选项在存在循环依赖时才能正常工作(其余都会引发ImportError
或AttributeError
)。一般情况下,你不应该使用第四种语法,因为它仅适用于Python 2,并有与其他第三方模块冲突的风险。所以,只有第一种语法是保证能够工作的。
编辑:在Python 3中,ImportError
和AttributeError
问题仅出现在Python 2中。在Python 3中,导入机制已被重写,所有这些导入语句(除第4种外)都可以正常工作,即使存在循环依赖。虽然本节中的解决方案可能有助于重构Python 3代码,但主要是为那些使用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
将所有import放在一个中心模块中
这种方法也可以,但与第一种方法存在同样的问题,即所有包和子模块的调用都变得非常冗长。 它还有两个主要缺点 - 它强制导入所有子模块,即使您只使用了一两个,而且您仍然无法查看任何子模块并快速查看其顶部的依赖项,必须浏览函数。
在__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()
2. 使用循环依赖导入对象时出错
在某些情况下,您可能能够导入具有循环依赖关系的模块,但是您将无法导入该模块中定义的任何对象,
或在导入模块的模块的顶级任何地方引用该导入的模块。但是,您可以在不会在导入时运行的函数和代码块中使用导入的模块。
例如,以下内容将起作用:
package/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"
但这行不通。
package/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
您将会得到一个异常
属性错误:模块'package'没有属性'a'
通常,在大多数有效的循环依赖情况下,可以重构或重新组织代码以防止这些错误并将模块引用移动到代码块内部。
abc
模块来解决这个问题吗?请参考https://docs.python.org/3/library/abc.html。 - undefined