包内导入并不总是有效

时间:2015-09-30 01:16:28

标签: python python-import circular-dependency

我有一个像这样结构的Django项目:

appname/
   models/
      __init__.py
      a.py
      base.py
      c.py

...其中appname / models / __ init__.py只包含如下语句:

from appname.models.base import Base
from appname.models.a import A
from appname.models.c import C

...以及appname / models / base.py包含的地方:

import django.db.models


class Base(django.db.models.Model):
   ...

和appname / models / a.py包含的地方:

import appname.models as models


class A(models.Base):
   ....

...同样适用于appname / models / c.py等。

我对我的代码结构非常满意,但由于循环导入,它当然不起作用。

当运行appname / __ init__.py时,appname / models / a.py将运行,但该模块导入" appname.models",尚未完成执行。经典循环导入。

所以这应该表明我的代码结构很差,需要重新设计以避免循环依赖。

有什么选择呢?

我能想到的一些解决方案,以及为什么我不想使用它们:

  1. 将我的所有模型代码合并到一个文件中:在我看来,在同一个文件中有20多个类的风格比我尝试做的更糟糕(使用单独的文件)。
  2. 移动" Base"将类打包到" appname / models"之外的另一个包中:这意味着我最终会在我的项目中包含包含基类/父类的包,这些类理想情况下应该拆分成其子/子包中的包课程位于。为什么我应该在同一个包中有模型,表单,视图等的基类/父类,而不是在它们自己的包中(子/子类所在的位置),而不是为了避免循环导入?
  3. 所以我的问题不仅仅是如何避免循环导入,而是以与我试图实现的方式一样干净(如果不是更干净)的方式这样做。

    有没有人有更好的方法?

1 个答案:

答案 0 :(得分:4)

修改

我已经对此进行了更彻底的研究,并得出结论,这是核心Python或Python文档中的错误。有关详细信息,请at this question and answer

Python的PEP 8表明对绝对超过相对进口的明确偏好。此问题的解决方法涉及相对导入,并且导入机制中可能存在修复。

我在下面的原始答案给出了示例和解决方法。

原始回答

正如您所正确推断的那样,问题是循环依赖。在某些情况下,Python可以很好地处理这些问题,但如果嵌套导入过多,则会出现问题。

例如,如果你只有一个包级别,实际上很难让它破解(没有相互导入),但是一旦你嵌套包,它就更像是相互导入,它开始成为难以使它工作。这是一个引发错误的例子:

level1/__init__.py

    from level1.level2 import Base

level1/level2/__init__.py

    from level1.level2.base import Base
    from level1.level2.a import A

level1/level2/a.py

    import level1.level2.base
    class A(level1.level2.base.Base): pass

level1/level2/base

    class Base: pass

错误可以通过几种不同的方式“修复”(对于这个小案例),但许多潜在的修复都很脆弱。例如,如果您不需要在level2 A文件中导入__init__,则删除该导入将解决问题(并且您的程序稍后可以执行import level1.level2.a.A),但是如果你的软件包变得越来越复杂,你会再次看到错误。

Python有时可以很好地使这些复杂的导入工作,并且它们何时能够工作和不工作的规则根本不直观。一般规则是from xxx.yyy import zzz可能比import xxx.yyy更宽容,然后是xxx.yyy.zzz。在后一种情况下,解释器必须在检索yyy时将xxx绑定到xxx.yyy.zzz命名空间,但在前一种情况下,解释器可以遍历模块完全设置顶级包命名空间之前的包。

因此,对于这个例子,真正的问题是a.py中的裸导入。这很容易修复:

    from level1.level2.base import Base
    class A(Base): pass

持续使用相对导入是强制使用from ... import的一种好方法,原因很简单,相对导入在from'. To use relative imports with the example above, level1 / level2 / a.py`应该包含的情况下不起作用:< / p>

from .base import Base
class A(Base): pass

这打破了有问题的导入周期,其他一切正常。如果导入的名称(例如Base)在没有以源模块名称作为前缀时过于混乱,则可以在导入时轻松地重命名:

from .base import Base as BaseModel
class A(BaseModel): pass

虽然这解决了当前的问题,但如果包结构变得更复杂,您可能需要考虑更普遍地使用相对导入。例如,level1/level2/__init__.py可以是:

from .base import Base
from .a import A