views:

74

answers:

3

Say I have three files like this:

testimports module:

#import moduleTwo
import moduleOne

hiString = "Hi!"

moduleOne.sayHi()

moduleOne:

import moduleTwo

class sayHi():
    moduleTwo.printHi()

moduleTwo:

import testimports

def printHi():
    print(testimports.hiString)

If I run testimports, I get the following:

Traceback (most recent call last):
  File "..file path snipped../testimports/src/testimports.py", line 2, in <module>
    import moduleOne
  File "..file path snipped../testimports/src/moduleOne.py", line 1, in <module>
    import moduleTwo
  File "..file path snipped../testimports/src/moduleTwo.py", line 1, in <module>
    import testimports
  File "..file path snipped../testimports/src/testimports.py", line 6, in <module>
    moduleOne.sayHi()
AttributeError: 'module' object has no attribute 'sayHi'

If, however, I uncomment the import moduleTwo line in testimports, the program gets to this point before it stops working:

Traceback (most recent call last):
  File "..file path snipped../testimports/src/testimports.py", line 1, in <module>
    import moduleTwo
  File "..file path snipped../testimports/src/moduleTwo.py", line 1, in <module>
    import testimports
  File "..file path snipped../testimports/src/testimports.py", line 2, in <module>
    import moduleOne
  File "..file path snipped../testimports/src/moduleOne.py", line 3, in <module>
    class sayHi():
  File "..file path snipped../testimports/src/moduleOne.py", line 4, in sayHi
    moduleTwo.printHi()
AttributeError: 'module' object has no attribute 'printHi'

How would I go about resolving this circular dependency problem?

+1  A: 

Rewriting testimports.py may help:

import moduleOne

hiString = "Hi!"

def main ():
    moduleOne.sayHi()

if __name__ == "__main__":
    main ()
Anatoly Rr
Thanks! It worked! :D
wrongusername
+2  A: 

Your problem is that when Python imports something it executes all statements at the base level. hiString is assigned to again and the call is made again when Module3 imports your original testimports.py as a module. Anatoly Rr's solution works because the call is now inside a def. The def is not called because the __name__ indicates to the Python runtime that the module is being imported. When it is being called from the command line it's module name would be __main__.

verisimilidude
Not actually accurate. For example, hiString is not assigned again but there will be two hiStrings. And in both cases there will be a _\_main_\_ in addition to the imported testimports. See my answer for details.
Muhammad Alkarouri
+2  A: 

verisimilidude is along the right direction. I would expand a little to give more details.

In both cases, this is what happens:

  1. testimports is executed as __main__
  2. testimports imports moduleOne. Now moduleOne is read from file and added to the list of imported modules sys.modules.
  3. The execution of importing of moduleOne starts by importing moduleTwo. Now moduleTwo is read from file and added to the list of imported modules sys.modules. Note at this stage the rest of moduleOne hasn't been executed yet so sayHi is not defined.
  4. Now moduleTwo starts execution by importing testimports. This is the first time testimports is imported and it has nothing to do with it being the same as __main__. It is now inserted into sys.modules.
  5. Here is where things get interesting. The newly imported testimports imports moduleOne. But moduleOne is already in sys.modules, so it is not read again. Then the execution progresses to the line moduleOne.sayHi(). But the importing of moduleOne hasn't finished and sayHi is not yet defined. Hence we get the error.

A similar loop happens if moduleTwo is uncommented. In essence, __main__ imports moduleTwo which imports testimports which passes the import of moduleTwo which is already imported and imports moduleOne; which in turn imports moduleTwo and then tries to call moduleTwo.printHi which isn't defined yet because moduleTwo hasn't finished execution all this time.

Anatoly Rr's solution breaks all this by making the module testimports not call any other module functionality when imported. So when it is imported by moduleTwo it doesn't call moduleOne.sayHi. Only when started as __main__ will it execute that so that execution is delayed after all the imports.

The moral of the story? Avoid circular dependencies in Python if possible.

Muhammad Alkarouri