user.py:
from story import Story
class User:
...
def get_stories(self):
story_ids = [select from database]
return [Story.get_by_id(id) for id in story_ids]
story.py
from user import User
class Story:
...
def __init__(self, id, user_id, content):
self.id = id
self.user = User.get_by_id(user_id)
self.content = content
如您所见,此程序中存在循环导入,导致ImportError
。我了解到我可以在方法定义中移动import语句以防止出现此错误。但我还是想知道,在这种情况下有没有办法删除循环导入,或者,是否有必要(对于一个好的设计)?
答案 0 :(得分:1)
减轻循环的另一种方法是更改导入样式。将from story import Story
更改为import story
,然后将该类称为story.Story
。由于您只引用方法中的类,因此在调用该方法之前不需要访问该类,此时导入将成功完成。 (您可能必须在其中一个或两个模块中进行此更改,具体取决于首先导入的模块。)
User
和Story
类非常紧密耦合 - 两者都不能在没有另一个的情况下使用。在这种情况下,将它们放在同一模块中通常更有意义。
答案 1 :(得分:1)
在这种情况下最明显的解决方案是通过更改接口来完全打破对User
类的依赖性,以便Story
构造函数接受实际的User
,而不是{ {1}}。这也可以带来更高效的设计:例如,如果用户有很多故事,则可以为所有这些构造函数提供相同的对象。
除此之外,整个模块(即user_id
和story
而不是成员)的导入应该有效 - 首先导入的模块在第二个模块时将显示为空进口;但这并不重要,因为这些模块的内容并未在全球范围内使用。
这比在方法中导入稍微优先。在一个方法中导入比仅仅一个模块全局查找(user
)有很大的开销,因为它需要为每个方法调用完成;似乎在一个简单的情况下,开销至少是30倍。
答案 2 :(得分:1)
网上有很多这些python
循环导入问题。我选择为此线程做出贡献,因为查询有一个Ray Hettinger的评论,它使循环导入的用例合法化,但推荐一个我认为不是特别好的做法的解决方案 - 将导入移动到方法。
除了Hettinger的权威之外,还有三个不同意见的免责声明是必要的:
此外,我认为可维护性和可读性要求将导入分组到文件的顶部,对于每个需要的名称只发生一次,并且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
中的import语句替换为:
try: from path import Line
except ImportError: pass # skip circular import second pass
追溯中的操作顺序仍在发生。只是第二次通过,我们忽略了导入失败。这没关系,因为Line
未在模块级别使用。因此,surface
所需的命名空间会加载到path
中。因此,path
的名称空间解析可以完成,允许将其加载到surface
,完成与from path import Line
的第一次相遇。因此,surface
的名称空间解析可以继续并完成,继续进行可能需要的任何其他操作。
这是一个简单而非常明确的习语。 try: ... except ...
语法清楚简洁地记录了循环导入问题,简化了未来可能需要的维护。每当重构确实是一个坏主意时使用它。
答案 3 :(得分:0)
正如BrenBarn所说,最明显的解决方案是将User和Story保持在同一个模块中,如果User应该对Story有所了解,那就非常有意义了。现在,如果确实需要将它们放在不同的模块中,您还可以在story.py中对用户进行monkeypatch以添加get_stories
方法。这是一种可读性/脱钩权交易......