python: Two modules and classes with the same name under different packages
Asked Answered
D

4

41

I have started to learn python and writing a practice app. The directory structure looks like

src
 |
 --ShutterDeck
    |
    --Helper
       |
       --User.py -> class User
    --Controller
       |
       --User.py -> class User

The src directory is in PYTHONPATH. In a different file, lets say main.py, I want to access both User classes. How can I do it.

I tried using the following but it fails:

import cherrypy
from ShutterDeck.Controller import User
from ShutterDeck.Helper import User

class Root:
  @cherrypy.expose
  def index(self):
    return 'Hello World'

u1=User.User()
u2=User.User()

That's certainly ambiguous. The other (c++ way of doing it) way that I can think of is

import cherrypy
from ShutterDeck import Controller
from ShutterDeck import Helper

class Root:

  @cherrypy.expose
  def index(self):
    return 'Hello World'

u1=Controller.User.User()
u2=Helper.User.User()

But when above script is run, it gives the following error

u1=Controller.User.User()
AttributeError: 'module' object has no attribute 'User'

I'm not able to figure out why is it erroring out? The directories ShutterDeck, Helper and Controller have __init__.py in them.

Dissection answered 30/3, 2013 at 16:6 Comment(0)
N
61

You want to import the User modules in the package __init__.py files to make them available as attributes.

So in both Helper/__init_.py and Controller/__init__.py add:

from . import User

This makes the module an attribute of the package and you can now refer to it as such.

Alternatively, you'd have to import the modules themselves in full:

import ShutterDeck.Controller.User
import ShutterDeck.Helper.User

u1=ShutterDeck.Controller.User.User()
u2=ShutterDeck.Helper.User.User()

so refer to them with their full names.

Another option is to rename the imported name with as:

from ShutterDeck.Controller import User as ControllerUser
from ShutterDeck.Helper import User as HelperUser

u1 = ControllerUser.User()
u2 = HelperUser.User()
Neoclassic answered 30/3, 2013 at 16:8 Comment(0)
B
13

One way is just:

import ShutterDeck.Controller.User
import ShutterDeck.Helper.User

cuser = ShutterDeck.Controller.User.User()
huser = ShutterDeck.Helper.User.User()

You can also do this:

from ShutterDeck.Controller.User import User as ControllerUser
from ShutterDeck.Helper.User import User as HelperUser
Bounteous answered 30/3, 2013 at 16:9 Comment(0)
C
2

This might also help (struggled with similar problem today):

ShutterDeck
├── Controller
│   ├── __init__.py
│   └── User.py
├── Helper
│   ├── __init__.py
│   └── User.py
└── __init__.py

in ShutterDeck/{Controller,Helper}/__init__.py:

from .User import User

And then:

>>> import ShutterDeck.Helper
>>> helperUser = ShutterDeck.Helper.User()
>>> helperUser
<ShutterDeck.Helper.User.User object at 0x1669b90>
>>> import ShutterDeck.Controller
>>> controllerUser = ShutterDeck.Controller.User()
>>> controllerUser
<ShutterDeck.Controller.User.User object at 0x1669c90>
Creaturely answered 26/10, 2013 at 11:54 Comment(0)
M
0

You can engineer sys.modules to do this.

If you have two files, version_file.py, which contain version information for two different modules/projects.

Import the first:

# NB use sys.path.insert(0, ...) to make this path the first path to be searched by the framework
sys.path.insert(0, str(project_a_dir_path)) # NB parent dir of version_file.py   
if 'version_file' in sys.modules:
    # ... deal with this: raise exception, delete key, log, etc. (see explanation below)
project_a_version_file_mod = importlib.import_module('version_file')
# make your own chosen key point to this newly imported module:
sys.modules['project_a_version_file_mod'] = sys.modules['version_file']
del sys.modules['version_file']
# preferably, delete from sys.path if this entry won't be used again:
del sys.path[0]

Then you process the other:

sys.path.insert(0, str(project_b_dir_path)) # NB parent dir of **its** version_file.py   
project_b_version_file_mod = importlib.import_module('version_file')
sys.modules['project_b_version_file_mod'] = sys.modules['version_file']
del sys.modules['version_file']
# preferably, delete entry from sys.path
del sys.path[0]

You now have two imported modules, project_a_version_file_mod and project_b_version_file_mod. Assuming both have an attr "version" you can get their respective values: project_a_version_file_mod.version and project_b_version_file_mod.version.

I'm not seeing anything problematic in this technique but would be interested to hear if anyone can point out any issues.

Idiosyncracies and traps of the sys.modules dict
NB it important to know, according to my experiments, that if importlib.import_module looks at sys.modules and finds a key there already for the next module it's being asked to add, it just ignores the new instruction! I find this quite strange, but that appears to be the case.

For this reason, if you don't delete (del sys.modules['version_file']) in the first import operation above, the second will fail (silently) to import on import_module, so in that case project_b_version_file_mod would in fact also be pointing at the version file module project_a_version_file_mod.

Similarly, if there is already an entry with key "version_file" before the first operation, the import operation wouldn't happen. That is why it is important to deal with that eventuality.

Idiosyncracies and traps of the sys.path list
NB2 Above, I recommend removing the entry added to sys.path enabling the module to be imported, after doing the import. The objection might then be raised "but supposing that added path was already present in sys.path?".

The answer to that is that sys.path is a list, not a set, so you can actually have multiple identical path entries, which is generally an undesirable thing, but which in this case makes this handling rather simple: by removing the added path you can guarantee you will return sys.path to the way it was before.

Macnair answered 23/5, 2023 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.