在网上有很多这样的关于
python
循环导入问题的讨论。我选择参与这个线程是因为Ray Hettinger的评论使得循环导入的使用情况合法化,但他推荐的解决方案我认为并不是特别好的做法——将导入移到一个方法中。
除了Hettinger的权威性外,还需要三个免责声明来回应普遍的反对意见:
1. 我从未在Java中编程,我不想使用Java风格。
2. 重构并不总是有用或有效的。逻辑API有时会决定一种结构,使得递归导入引用不可避免。请记住,代码存在于用户而非程序员。
3. 合并相当大的模块可能会导致可读性和可维护性问题,这可能比一个或两个递归导入更糟糕。
此外,我认为可维护性和可读性要求将导入分组放在文件顶部,每个所需名称只出现一次,并且
from module import name
风格是首选(也许除了具有许多函数的非常短的模块名,例如
gtk
),因为它避免了重复的语言混乱并使依赖关系明确。
在这个基础上,我将提出一个简化版的我的用例,并介绍我的解决方案。
我有两个模块,每个模块都定义了许多类。 surface
定义了几何表面,如平面、球体、双曲面等。 path
定义了平面几何图形,如直线、圆、双曲线等。从 API 要求的角度来看,这些逻辑上是不同的类别,重构并不是一个选项。然而,这两个类别是紧密相关的。
一个有用的操作是相交两个表面,例如,两个平面的相交是一条直线,或者一个平面和一个球体的相交是一个圆。
例如,如果在 surface.py
中进行直接导入以实现相交操作的返回值:
from path import Line
你得到:
你得到:
Traceback (most recent call last):
File "surface.py", line 62, in <module>
from path import Line
File ".../path.py", line 25, in <module>
from surface import Plane
File ".../surface.py", line 62, in <module>
from path import Line
ImportError: cannot import name Line
几何上,平面用于定义路径,毕竟它们可以在三维(或更多)中任意定位。回溯告诉您发生了什么以及解决方法。
只需将
surface.py
中的导入语句替换为:
try: from path import Line
except ImportError: pass
跟踪回溯的操作仍在进行中。只是第二次通过时,我们忽略了导入失败。这并不重要,因为
Line
在模块级别上没有使用。因此,
surface
的必要命名空间被加载到
path
中。因此,
path
的命名空间解析可以完成,允许将其加载到
surface
中,完成首次遇到
from path import Line
。因此,
surface
的命名空间解析可以继续并完成,继续进行可能需要的任何其他操作。
这是一种简单而非常清晰的习惯用语。
try: ... except ...
语法清晰而简洁地记录了循环导入问题,减轻了未来可能需要的任何维护工作。每当重构确实是一个不好的想法时,请使用它。