问题
I have a testing environment to try to understand how python circular dependencies can be avoided importing the modules with an import x
statement, instead of using a from x import y
:
test/
__init__.py
testing.py
a/
__init__.py
m_a.py
b/
__init__.py
m_b.py
The files have the following content:
testing.py:
from a.m_a import A
m_a.py:
import b.m_b
print b.m_b
class A:
pass
m_b.py:
import a.m_a
print a.m_a
class B:
pass
There is a situation which I can't understand:
If I remove the print statements from modules m_a.py
and m_b.py
or only from m_b.py
this works OK, but if the print is present at m_b.py
, then the following error is thrown:
File "testing.py", line 1, in <module>
from a.m_a import A
File "/home/enric/test/a/m_a.py", line 1, in <module>
import b.m_b
File "/home/enric/test/b/m_b.py", line 3, in <module>
print a.m_a
AttributeError: 'module' object has no attribute 'm_a'
Do you have any ideas?
回答1:
It only "works" with the print statements removed because you're not actually doing anything that depends on the imports. It's still a broken circular import.
Either run this in the debugger, or add a print
statement after each line, and you'll see what happens:
- testing.py:
from a.m_a import A
- a.m_a:
import b.m_b
- b.m_b:
import a.m_a
- b.m_b:
print a.m_a
It's clearly trying to access a.m_a
before the module finished importing. (In fact, you can see the rest of a.m_a
on the stack in your backtrace.)
If you dump out sys.modules
at this point, you'll find two partial modules named a
and a.m_a
, but if you dir(a)
, there's no m_a
there yet.
As far as I can tell, the fact that m_a
doesn't get added to a
until m_a.py
finishes evaluating is not documented anywhere in the Python 2.7 documentation. (3.x has much a more complete specification of the import process—but it's also a very different import process.) So, you can't rely on this either failing or succeeding; either one is perfectly legal for an implementation. (But it happens to fail in at least CPython and PyPy…)
More generally, using import foo
instead of from foo import bar
doesn't magically solve all circular-import problems. It just solves one particular class of circular-import problems (or, rather, makes that class moot). (I realize there is some misleading text in the FAQ about this.)
There are various tricks to work around circular imports while still letting you have circular top-level dependencies. But really, it's almost always simpler to get rid of the circular top-level dependencies.
In this toy case, there's really no reason for a.m_a
to depend on b.m_b
at all. If you need some that prints out a.m_a
, there are better ways to get it than from a completely independent package!
In real-life code, there probably is some stuff in m_a
that m_b
needs and vice-versa. But usually, you can separate it out into two levels: stuff in m_a
that needs m_b
, and stuff in m_a
that's needed by m_b
. So, just split it into two modules. It's really the same thing as the common fix for a bunch of modules that try to reach back up and import main
: split a utils
off main
.
What if there really is something that m_b
needs from m_a
, that also needs m_b
? Well, in that case, you may have to insert a level of indirection. For example, maybe you can pass the thing-from-m_b
into the function/constructor/whatever from m_a
, so it can access it as a local parameter value instead of as a global. (It's hard to be more specific without a more specific problem.)
If worst comes to worst, and you can't remove the import via indirection, you have to move the import out of the way. That may again mean doing an import inside a function call, etc. (as explained in the FAQ immediately after the paragraph that set you off), or just moving some code above the import, or all kinds of other possibilities. But consider these last-ditch solutions to something which just can't be designed cleanly, not a roadmap to follow for your designs.
来源:https://stackoverflow.com/questions/18156500/experiment-trying-to-avoid-python-circular-dependencies