简体   繁体   English

包内进口并不总是有效

[英]Intra-package imports do not always work

I have a Django project structured like so: 我有一个像这样结构的Django项目:

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

... where appname/models/__init__.py contains only statements like so: ... appname / models / __ init__.py只包含这样的语句:

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

... and where appname/models/base.py contains: ...以及appname / models / base.py包含的位置:

import django.db.models


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

and where appname/models/a.py contains: 以及appname / models / a.py包含的位置:

import appname.models as models


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

...and similarly for appname/models/c.py, etc.. ...同样适用于appname / models / c.py等。

I am quite happy with this structure of my code, but of course it does not work, because of circular imports. 我很满意我的代码结构,但当然它不起作用,因为循环导入。

When appname/__init__.py is run, appname/models/a.py will get run, but that module imports "appname.models", which has not finished executing yet. 当appname / __ init__.py运行时,appname / models / a.py将运行,但该模块导入“appname.models”,尚未完成执行。 Classic circular import. 经典循环导入。

So this supposedly indicates that my code is structured poorly and needs to be re-designed in order to avoid circular dependency. 所以这应该表明我的代码结构很差,需要重新设计以避免循环依赖。

What are the options to do that? 有什么选择呢?

Some solutions I can think of and then why I don't want to use them: 我能想到的一些解决方案,以及为什么我不想使用它们:

  1. Combine all my model code into a single file: Having 20+ classes in the same file is a far worse style than what I am trying to do (with separate files), in my opinion. 将我的所有模型代码合并到一个文件中:在我看来,在同一个文件中有20多个类比我尝试做的(使用单独的文件)更糟糕。
  2. Move the "Base" model class into another package outside of "appname/models": This means that I would end up with package in my project that contains base/parent classes that should ideally be split into the packages in which their child/sub classes are located. 将“Base”模型类移动到“appname / models”之外的另一个包中:这意味着我最终会在我的项目中包含包含基类/父类的包,这些类理想情况下应该拆分为其子/子包中的包课程位于。 Why should I have base/parent classes for models, forms, views, etc. in the same package and not in their own packages (where the child/sub classes would be located), other than to avoid circular imports? 为什么我应该在同一个包中有模型,表单,视图等的基类/父类,而不是它们自己的包(子/子类所在的位置),除了避免循环导入?

So my question is not just how to avoid circular imports, but to do so in a way that is just as clean (if not cleaner) that what I tried to implement. 所以我的问题不仅仅是如何避免循环导入,而是以一种与我试图实现的方式一样干净(如果不是更干净)的方式。

Does anyone have a better way? 有没有人有更好的方法?

Edit 编辑

I have researched this more thoroughly and come to the conclusion that this is a bug in either core Python or the Python documentation. 我已经对此进行了更彻底的研究,并得出结论,这是核心Python或Python文档中的错误。 More information is available at this question and answer . 此问题和答案提供更多信息。

Python's PEP 8 indicates a clear preference for absolute over relative imports. Python的PEP 8表明绝对优先于绝对超过相对进口。 This problem has a workaround that involves relative imports, and there is a possible fix in the import machinery. 此问题有一个涉及相对导入的解决方法,并且导入机制中可能存在修复。

My original answer below gives examples and workarounds. 我在下面的原始答案提供了示例和解决方法。

Original answer 原始答案

The problem, as you have correctly deduced, is circular dependencies. 正如您所正确推断的那样,问题是循环依赖。 In some cases, Python can handle these just fine, but if you get too many nested imports, it has issues. 在某些情况下,Python可以很好地处理这些,但是如果你得到太多的嵌套导入,它就会出现问题。

For example, if you only have one package level, it is actually fairly hard to get it to break (without mutual imports), but as soon as you nest packages, it works more like mutual imports, and it starts to become difficult to make it work. 例如,如果你只有一个包级别,实际上很难让它破坏(没有相互导入),但是一旦你嵌套包,它就更像是相互导入,并且开始变得难以制作这行得通。 Here is an example that provokes the error: 这是一个引发错误的示例:

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

The error can be "fixed" (for this small case) in several different ways, but many potential fixes are fragile. 错误可以通过几种不同的方式“修复”(对于这个小案例),但许多潜在的修复都很脆弱。 For example, if you don't need the import of A in the level2 __init__ file, removing that import will fix the problem (and your program can later execute import level1.level2.aA ), but if your package gets more complex, you will see the errors creeping in again. 例如,如果您不需要在level2 __init__文件中导入A ,则删除该导入将解决问题(并且您的程序稍后可以执行import level1.level2.aA ),但如果您的包变得更复杂,那么将再次看到错误蔓延。

Python sometimes does a good job of making these complex imports work, and the rules for when they will and won't work are not at all intuitive. Python有时可以很好地使这些复杂的导入工作,并且它们何时能够工作和不工作的规则根本不直观。 One general rule is that from xxx.yyy import zzz can be more forgiving than import xxx.yyy followed by xxx.yyy.zzz . 一般规则是from xxx.yyy import zzz可以比import xxx.yyy更加宽容,然后是xxx.yyy.zzz In the latter case, the interpreter has to have finished binding yyy into the xxx namespace when it is time to retrieve xxx.yyy.zzz , but in the former case, the interpreter can traverse the modules in the package before the top-level package namespace is completely set up. 在后一种情况下,解释器必须在检索xxx.yyy.zzz时完成将yyy绑定到xxx命名空间,但在前一种情况下,解释器可以在顶层包之前遍历包中的模块命名空间已完全设置。

So for this example, the real problem is the bare import in a.py This could easily be fixed: 所以对于这个例子,真正的问题是a.py的裸导入这很容易修复:

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

Consistently using relative imports is a good way to enforce this use of from ... import for the simple reason that relative imports do not work without the from'. To use relative imports with the example above, 始终使用相对导入是强制使用from ... import一种好方法,原因很简单,相对导入在没有from'. To use relative imports with the example above,情况下不起作用from'. To use relative imports with the example above, from'. To use relative imports with the example above, level1/level2/a.py` should contain: from'. To use relative imports with the example above, level1 / level2 / a.py`应包含:

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

This breaks the problematic import cycle and everything else works fine. 这打破了有问题的导入周期,其他一切正常。 If the imported name (such as Base) is too confusingly generic when not prefixed with the source module name, you can easily rename it on import: 如果导入的名称(例如Base)在没有以源模块名称作为前缀时过于混乱,则可以在导入时轻松地重命名:

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

Although that fixes the current problem, if the package structure gets more complex, you might want to consider using relative imports more generally. 虽然这解决了当前的问题,但如果包结构变得更复杂,您可能需要考虑更普遍地使用相对导入。 For example, level1/level2/__init__.py could be: 例如, level1/level2/__init__.py可以是:

from .base import Base
from .a import A

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM