删除python循环导入

时间:2013-11-15 07:49:38

标签: python import circular-dependency

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

4 个答案:

答案 0 :(得分:1)

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

然而,设计似乎有些奇怪。您的设计使得UserStory类非常紧密耦合 - 两者都不能在没有另一个的情况下使用。在这种情况下,将它们放在同一模块中通常更有意义。

答案 1 :(得分:1)

在这种情况下最明显的解决方案是通过更改接口来完全打破对User类的依赖性,以便Story构造函数接受实际的User,而不是{ {1}}。这也可以带来更高效的设计:例如,如果用户有很多故事,则可以为所有这些构造函数提供相同的对象。

除此之外,整个模块(即user_idstory而不是成员)的导入应该有效 - 首先导入的模块在第二个模块时将显示为空进口;但这并不重要,因为这些模块的内容并未在全球范围内使用。

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

答案 2 :(得分:1)

网上有很多这些python循环导入问题。我选择为此线程做出贡献,因为查询有一个Ray Hettinger的评论,它使循环导入的用例合法化,但推荐一个我认为不是特别好的做法的解决方案 - 将导入移动到方法。

除了Hettinger的权威之外,还有三个不同意见的免责声明是必要的:

  1. 我从未用Java编程。我不是想做Java风格。
  2. 重构并不总是有用或有效。逻辑API有时会规定一种结构,使得递归导入引用不可避免。请记住,代码存在于用户,而不是程序员。
  3. 组合相当大的模块会导致可读性和可维护性问题,这可能比一两个递归导入更糟糕。
  4. 此外,我认为可维护性和可读性要求将导入分组到文件的顶部,对于每个需要的名称只发生一次,并且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方法。这是一种可读性/脱钩权交易......