如何避免 Python 中的循环导入?[重复]
- 2025-01-03 08:41:00
- admin 原创
- 101
问题描述:
我知道 Python 中的循环导入问题之前已经出现过很多次,我也读过这些讨论。这些讨论中反复出现的评论是,循环导入是糟糕设计的标志,应该重新组织代码以避免循环导入。
有人能告诉我在这种情况下如何避免循环导入吗?:我有两个类,我希望每个类都有一个构造函数(方法),它接受另一个类的实例并返回该类的实例。
更具体地说,一个类是可变的,另一个是不可变的。不可变类用于哈希、比较等。可变类也用于执行操作。这类似于集合和冻结集或列表和元组。
我可以将两个类定义放在同一个模块中。还有其他建议吗?
一个玩具示例是类 A,它有一个属性,即列表;类 B,它有一个属性,即元组。然后类 A 有一个方法,它接受类 B 的一个实例并返回类 A 的一个实例(通过将元组转换为列表);同样,类 B 有一个方法,它接受类 A 的一个实例并返回类 B 的一个实例(通过将列表转换为元组)。
解决方案 1:
考虑以下示例 Python 包,其中a.py
和b.py
相互依赖:
/package
__init__.py
a.py
b.py
循环进口问题的类型
循环导入依赖项通常分为两类,具体取决于您要导入的内容以及在每个模块内使用它的位置。(以及您使用的是 Python 2 还是 3)。
循环导入模块时出错
在某些情况下,即使您没有引用导入模块中的任何内容,仅导入具有循环导入依赖项的模块也会导致错误。
在 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
不幸的是,当您有循环依赖项时,只有第 1 和第 4 个选项实际上有效(其余选项都引发ImportError
或AttributeError
)。一般来说,您不应该使用第 4 种语法,因为它只在 python2 中有效,并且有与其他第三方模块冲突的风险。所以实际上,只有第一种语法可以保证有效。
编辑:
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
将所有导入的内容放在中央模块中
这种方法也行得通,但存在与第一种方法相同的问题,即所有包和子模块调用都变得非常长。这种方法也有两个主要缺陷——它强制导入所有子模块,即使您只使用一个或两个子模块,您仍然无法查看任何子模块并在顶部快速查看它们的依赖关系,您必须仔细筛选函数。
在__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"
包/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
包/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”
通常,在大多数有效的循环依赖情况下,可以重构或重新组织代码以防止这些错误并在代码块内移动模块引用。
解决方案 2:
仅导入模块,不从模块导入:
考虑a.py
:
import b
class A:
def bar(self):
return b.B()
和b.py
:
import a
class B:
def bar(self):
return a.A()
这工作得很好。
解决方案 3:
我们结合了绝对导入和函数,以便更好地阅读并缩短访问字符串。
优点:与纯绝对导入相比,访问字符串更短
缺点:由于额外的函数调用,开销稍大
主/子/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__)
主要/子/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__)