如何避免Python中的循环导入?

BWW*_*BWW 98 python import circular-dependency

我知道python中循环导入的问题已经出现过很多次了,我已经阅读过这些讨论了.在这些讨论中反复提出的评论是,循环导入是设计错误的标志,应重新组织代码以避免循环导入.

有人可以告诉我如何在这种情况下避免循环导入吗?:我有两个类,我希望每个类都有一个构造函数(方法),它接受另一个类的实例并返回该类的实例.

更具体地说,一个类是可变的,一个是不可变的.散列,比较等需要不可变类.可变类也需要做事.这与sets和frozensets或者列表和元组类似.

我可以将两个类定义放在同一个模块中.还有其他建议吗?

玩具示例是类A,其具有属性是列表,而类B具有属性是元组.然后类A有一个方法,它接受类B的实例并返回类A的实例(通过将元组转换为列表),类似地,类B有一个方法,它接受类A的实例并返回类B的实例(通过将列表转换为元组).

Bre*_*bel 163

考虑以下示例python包在哪里a.pyb.py相互依赖:

/package
    __init__.py
    a.py
    b.py
Run Code Online (Sandbox Code Playgroud)

有几种方法可以在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
Run Code Online (Sandbox Code Playgroud)

不幸的是,只有第一个和第四个选项实际上在你有循环依赖关系时才会起作用(其余全部加注ImportErrorAttributeError).通常,您不应该使用第4种语法,因为它仅适用于python2并且存在与其他第三方模块冲突的风险.实际上,只有第一种语法才能保证正常工作.但是,在处理循环依赖项时,您仍有几个选项.

编辑:ImportErrorAttributeError问题只发生在python 2中.在python 3中,导入机制已经被重写,所有这些import语句(4除外)都可以工作,即使是循环依赖.

使用绝对进口

只需使用上面的第一个导入语法.这种方法的缺点是导入名称对于大型包来说可能会超长.

a.py

import package.b
Run Code Online (Sandbox Code Playgroud)

b.py

import package.a
Run Code Online (Sandbox Code Playgroud)

将导入推迟到以后

我已经看到这个方法在很多软件包中使用了,但它对我来说仍然感觉很讨厌,我不喜欢看不到模块的顶部并看到它的所有依赖项,我不得不去搜索所有的函数同样.

a.py

def func():
    from package import b
Run Code Online (Sandbox Code Playgroud)

b.py

def func():
    from package import a
Run Code Online (Sandbox Code Playgroud)

将所有导入放在中央模块中

这也有效,但是与第一种方法有同样的问题,其中所有的包和子模块调用都变得超长.它还有两个主要缺陷 - 它强制导入所有子模块,即使你只使用一个或两个,你仍然无法查看任何子模块并快速看到它们的依赖关系在顶部,你必须筛选功能.

__init__.py

from . import a
from . import b
Run Code Online (Sandbox Code Playgroud)

a.py

import package

def func():
    package.b.some_object()
Run Code Online (Sandbox Code Playgroud)

b.py

import package

def func():
    package.a.some_object()
Run Code Online (Sandbox Code Playgroud)

所以那些是你的选择(他们都有点吮吸IMO).坦率地说,这似乎是python导入机制中的一个明显的错误,但这仅仅是我的观点.

  • 布兰登,这是一个惊人的答案!我已经阅读了关于解决循环进口的一百个答案,最后我明白了.谢谢!顺便说一下,您可能希望在列表中再添加一个解决方案:将每个包的全局设置为文件顶部的无,然后在运行时将模块注入全局.这样做的好处是所有模块名称都位于文件顶部. (6认同)
  • 我对 Python 3 中已修复此问题的声明感到好奇。您是否有很好的参考资料来描述此更改的方式?我知道 2 和 3 之间的那个区域发生了变化,但是`from...import` 表单的一个[小例子](https://gist.github.com/robhague/d1417cb764c05dd8d3c9500c9fea0d92) 失败了在 Python 2.7.11 和 Python 3.5.1 上的方式。 (5认同)
  • 如果函数被反复调用,将延迟导入直到函数内部也***或者导入只发生一次? (2认同)
  • 如果'module_name'不是sys.modules:import ...`呢? (2认同)

rum*_*pel 88

只导入模块,不要从模块导入:

考虑a.py:

import b

class A:
    def bar(self):
        return b.B()
Run Code Online (Sandbox Code Playgroud)

并且b.py:

import a

class B:
    def bar(self):
        return a.A()
Run Code Online (Sandbox Code Playgroud)

这完全没问题.

  • 似乎不适用于子模块`import foobar.mod_a`和`import foobar.mod_b`不能像上面描述的那样工作. (39认同)
  • 社区:这个答案是不够的.请阅读下面的Brenden! (13认同)
  • 此外,这有一个很大的缺点:当您删除某个函数并忘记在某处更新对它的引用时,最终会出现运行时错误而不是导入时错误. (5认同)
  • 哇!!谢谢,我知道放入"from imports"之一的技巧导致模块底部的循环导入错误,但这更好! (2认同)

Chr*_*ntz 7

我们将绝对导入和函数组合在一起,以便更好地阅读和缩短访问字符串.

  • 优点:与纯绝对导入相比,访问字符串更短
  • 缺点:由于额外的函数调用会产生更多的开销

主/副/ 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__)
Run Code Online (Sandbox Code Playgroud)

主/副/ 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__)
Run Code Online (Sandbox Code Playgroud)

  • @BrendanAbel lambda延迟访问,直到需要使用它为止.没有它,就会引发AttributeError (5认同)
  • 请不要将 lambda 表达式分配给绑定名称。使用经典的“def”代替。 (4认同)
  • 为什么要使用`lambda`?为什么不只是`b_mod = main.sub.b`? (2认同)