Name collision between modules imported within third party modules

1.2k views Asked by At

Suppose mine.py wants to import moduleA and moduleB but moduleA and moduleB each try to import a module called "moduleC". These are two distinct modules that both happen to be named "moduleC". When mine.py is run, depending on sys.path either moduleA or moduleB gets the correct "moduleC", the other gets a surprise, and chaos ensues.

If moduleA and moduleB were written by different authors, neither of which is us, it is preferable to not modify those modules. Is there any solution available to the author of mine.py that does not modify moduleA or moduleB?

The following questions ask how to solve this issue when you are the author of moduleA or moduleB.

Importing from builtin library when module with same name exists

How to access a standard-library module in Python when there is a local module with the same name?

My specific case

I want to run a program called PyMOL under the Python debugger, pdb. PyMOL unfortunately has a "cmd.py" that it imports and that conflicts with the usual cmd which pdb imports.

The relevant parts of the PyMOL install look like this:

pymol/
    __init__.py
    cmd.py

PyMOL is run by executing __init__.py. This file then imports cmd as from pymol import cmd.

Working from what BrenBarn pointed out, so far I can get pdb to successfully import the correct cmd by temporarily removing the pymol directory from the front of sys.path. After that when PyMOL tries to import its cmd it crashes. Somehow I need to remove Python's cmd from the import module search before PyMOL imports but after pdb imports.

Minimal example

$ ls
pymol/
$ ls pymol/
__init__.py  cmd.py

init.py

# insert some code into __init__.py directly

import sys
pymol_path = sys.path[0]
sys.path[0] = ""
import pdb
sys.path[0] = pymol_path

from pymol import cmd

# test a sandwich of calls that require each "cmd" modules
pdb.set_trace()    
cmd.foo()    
pdb.set_trace()
cmd.foo()

print "done!"

# original PyMOL __init__.py code would follow

cmd.py

def foo():
    print("cmd.foo()")

Try it

$ PYTHONPATH= python ./pymol/__init__.py
> /Users/khouli/scr/pymol_scr/pymol/__init__.py(11)<module>()
-> cmd.foo()
(Pdb) continue
cmd.foo()
> /Users/khouli/scr/pymol_scr/pymol/__init__.py(13)<module>()
-> cmd.foo()
(Pdb) continue
cmd.foo()
done!

Edit: The method given above now seems to work but as BrenBarn's answer states, there likely is no solution that leaves all third party code unmodified as the question originally asked for. This is due to quirks in PyMOL.

1

There are 1 answers

3
BrenBarn On BEST ANSWER

Your problem is not just with the imports, but with the fact that you are running __init__.py as a script. When you run a script, Python adds the directory containing the script to the front of sys.path, and this globally affects all subsequent imports.

There is no way to customize anything if you are directly running a file that you don't want to modify. You can't do any sneaky sys.path manipulations unless you get to run your own code first, to set up the path the way you want it. If you import the file instead of running it, you have the possibility to use your own code to tweak the paths.

I suspect that this problem is to some degree specific to PyMOL, which unfortunately does not seem to be well-designed in this regard. Looking at the source code here, I see that PyMOL's __init__.py includes a lot of custom code that does weird things like import __main__ and checking whether the version of itself that is running has various attributes. You could try using the "unsupported/experimental" method described in the comments in that file, which involves importing PyMOL instead of running it. I don't know anything about PyMOL so I don't know how that would work.

It might be worth contacting the authors of PyMOL to suggest that they fix this.