Import inside the package does not always work

I have a Django project structured like this:

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

... where appname / models / __ init__.py contains only such statements:

 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:

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

and where appname / models / a.py contains:

 import appname.models as models class A(models.Base): .... 

... and similarly for appname / models / c.py, etc.

I am very pleased with this structure of my code, but of course this does not work due to circular import.

When appname / __ init__.py is started, the appname / models / a.py application will be launched, but this module imports "appname.models", which has not yet completed execution. Classic circular import.

So, this supposedly indicates that my code is poorly structured and needs to be re-developed in order to avoid circular dependency.

What are the options for this?

Some solutions that I can think of and why I do not want to use them:

  • Combine all my model code into one file: having 20+ classes in one file is much worse than what I'm trying to do (with separate files), in my opinion.
  • Move the "Base" model class to another package outside of "appname / models": this means that in the end I will have a package in my project that contains the base / parent classes, which ideally should be divided into packages in which their children / subclass. 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 / subclass classes will be located), except how to avoid circular imports?

So, my question is not only how to avoid cyclic import operations, but also that what I tried to implement is just as clean (if not cleaner).

Does anyone have a better way?

+5
source share
1 answer

Edit

I investigated this in more detail and came to the conclusion that this is a mistake both in the basic Python documentation and in the Python documentation. Further information is available in this question and answer .

Python PEP 8 points to a clear preference for absolute relative imports. This problem has a workaround related to relative imports, and there is a possible fix in imports.

In my original answer below are examples and workarounds.

Original answer

The problem, as you deduced correctly, is circular dependence. In some cases, Python can handle them just fine, but if you get too many nested imports, it has problems.

For example, if you have only one level of a package, it is actually quite difficult to make it break (without mutual import), but as soon as you insert packages, it looks more like mutual import, and it starts to become difficult to make it work. Here is an example that causes an 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 

A bug can be “fixed” (for this small case) in several ways, but many potential fixes are fragile. For example, if you do not need to import A in the file level2 __init__ , deleting this import will fix the problem (and your program may later execute import level1.level2.aA ), but if your package becomes more complex, you will see errors creeping in again.

Python sometimes does a good job of creating these complex import jobs, and the rules for when they will and will not work are not at all intuitive. One general rule is that from xxx.yyy import zzz can be more forgiving than import xxx.yyy followed by xxx.yyy.zzz . In the latter case, the interpreter had to complete the binding of yyy to the xxx namespace when it was time to get xxx.yyy.zzz , but in the first case, the interpreter could move the modules in the package before, for the full package namespace.

So, for this example, the real problem is bare imports in a.py This can be easily fixed:

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

Constantly using relative imports is a good way to enforce this use from ... import for the simple reason that relative imports do not work without 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 .base import Base class A(Base): pass 

This breaks the problematic import cycle, and everything else works fine. If the imported name (for example, Base) is too confusing for the generic name, if it does not have a prefix with the name of the source module, you can easily rename it when importing:

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

Although this fixes the current problem, if the package structure becomes more complex, you might want to consider using relative imports more generally. For example, level1/level2/__init__.py could be:

 from .base import Base from .a import A 
+4
source

Source: https://habr.com/ru/post/1232581/