9

用户.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]

故事.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 语句来防止这个错误。但我仍然想知道,在这种情况下有没有办法删除循环导入,或者,是否有必要(对于一个好的设计)?

4

4 回答 4

1

减轻循环性的另一种方法是更改​​导入样式。更改from story import Storyimport story,然后将类称为story.Story。由于您仅在方法中引用类,因此在调用该方法之前不需要访问该类,此时导入将成功完成。(您可能必须在其中一个或两个模块中进行此更改,具体取决于首先导入哪个模块。)

然而,这个设计确实有点奇怪。您的设计使得UserStory类非常紧密地耦合——没有另一个就不能使用。在这种情况下,将它们放在同一个模块中通常更有意义。

于 2013-11-15T07:53:02.893 回答
1

在这种情况下,最明显的解决方案是完全打破对User类的依赖,通过更改接口使Story构造函数接受一个实际的User,而不是一个user_id。这也导致了更有效的设计:例如,如果用户有很多故事,则可以将相同的对象提供给所有这些构造函数。

除此之外,整个模块(即story,而user不是成员)的导入应该可以工作-首先导入的模块在导入第二个模块时将显示为空;但是这并不重要,因为这些模块的内容不在全局范围内使用。

这比在方法中导入要好一些。在一个方法中导入比模块全局查找(story.Story)有很大的开销,因为它需要为每个方法调用完成;似乎在一个简单的情况下,开销至少是 30 倍。

于 2013-11-15T18:15:39.183 回答
1

网上有很多这样的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

在几何上,平面用于定义路径,毕竟,它们可以在三个(或更多)维度上任意定向。回溯会告诉您正在发生的事情和解决方案。

只需将 import 语句替换为surface.py

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 ...语法清晰简洁地记录了循环导入问题,简化了未来可能需要的任何维护。每当重构确实是一个坏主意时使用它。

于 2017-12-06T03:47:32.273 回答
0

正如 BrenBarn 所说,最明显的解决方案是将 User 和 Story 保持在同一个模块中,如果 User 应该知道 Story 的任何内容,这非常有意义。现在,如果您确实需要将它们放在不同的模块中,您还可以在 story.py 中使用monkeypatch User 来添加该get_stories方法。这是一个可读性/解耦的权衡......

于 2013-11-15T11:34:11.253 回答