circular imports (don't yell!)

Gordon McMillan gmcm at hypernet.com
Sat Mar 23 10:05:18 EST 2002


jimh wrote:

> We have a scenario where we have (and need) circular imports, that is,
> A imports B imports C imports A.  Now I know there are ways around
> this, but we have external dependencies that make it very difficult to
> use an alternative, so don't be yelling at me about the design ;)
> 
> I have whittled the problem down to 3 files:
> 
> a.py
> ------
> from b import *
> from c import *
> consta = 3
> if __name__ == "__main__":
>    print consta
>    print constb
>    print constc
> 
> b.py
> ------
> print "in b"
> from c import *
> constb = 4
> constb2 = constc + 10
> 
> c.py
> ------
> print "in c"
> from a import *
> constc = 5
> 
> As these are given here, it works.  

Note that a.py is running as __main__. The
import in c.py gets a different copy (check
out sys.modules at the end to prove it). So
you don't have a circular import.

 __main__ imports b
 b imports c
 c imports a
 a imports b (already loaded)
 a imports c (already loaded)
 __main__ imports c (already loaded)

> But if I switch the order of the
> two imports in a.py, it doesn't work:
> 
> Traceback (most recent call last):
>   File "a.py", line 2, in ?
>     from c import *
>   File "c.py", line 2, in ?
>     from a import *
>   File "a.py", line 3, in ?
>     from b import *
>   File "b.py", line 5, in ?
>     constb2 = constc + 10
> NameError: name 'constc' is not defined
> 
> I think this is what is happening:
>   - a imports c so c is marked as "loaded"

Not exactly. In effect, c is marked as "loading".

>   - c imports a
>   - a imports b
>   - b imports c, but c is already marked as "loaded", however, it
>   hasn't 
> REALLY been loaded, thus constc is not yet defined.

About right (the import of c in b doesn't do anything,
because c is "loading"). But you haven't accounted for
the difference between __main__ and a.

 __main__ imports c
 c imports a
 a imports c (loading)
 a imports b
 b imports c (loading)
and you die here because b wants c.constc.

So changing b to
 
 print "in b"
 import c 
 constb = 4
 constb2 = c.constc + 10

gets you closer, but still doesn't work. That's 
because c hasn't finished loading at the time
c.constc is evaluated. But that's probably an
artifact of your test. In real life, references
to c.contc are likely to be inside functions or
methods, and the revised code would work.

The lesson being that with circular imports, 
not everyone can use "from ... import ...".
Somebody has to do a plain "import ...".

(And don't forget __main__).

-- Gordon
http://www.mcmillan-inc.com/





More information about the Python-list mailing list