Question

I have following structure:

app.py

modules
    |
    -- __init__.py
    |
    -- update.py

I try to call function from modules/update.py dynamicaly:

import sys

arguments = sys.argv
module = arguments[1]
command = arguments[2]
moduleObj = __import__('modules.'+module)            # returns module object "modules"
submodule = getattr(moduleObj, module)    # returns the module object "modules.update"
moduleClass = getattr(submodule, module)  # returns the class object "update"
    # this works because the class has the same name as the file it contains it.
    # if the names are different then you have to change that accordingly 
result = getattr(moduleClass, command) # returns the attribute with the name =command
print result()

but I catch a error:

File "app.py", line 12, in print result() TypeError: unbound method test() must be called with update instance as first argument (got nothing instead)

What's wrong? Help me please!

ps: update.py code:

import os

class update:
    def repositories(self):
        os.system('sudo apt-get update')

    def system(self):
        os.system('sudo apt-get -y upgrade')

    def distributive(self):
        os.system('sudo apt-get -y dist-upgrade')

    def all(self):
        self.repositories()
        self.system()
        self.distributive()

    def test(self):
        print 'test complete!'
Was it helpful?

Solution

I think that you are passing the arguments like this:

python app.py update attribute

so your program interprets it like this:

getattr(__import__('modules.update.update'),command)()

as you don't have a module with that name you get that import error.

If I understood correctly you have the following files structure:

app.py

modules
    |
    -- __init__.py
    |
    -- update.py

Edit: let me suppose for the sake of explaining, that your update.py contains:

class update(object):
    name = 'update_name'

now in your app.py:

import sys

arguments = sys.argv
module = arguments[1]
command = arguments[2]
moduleObj = __import__(module)            # returns module object "modules"
submodule = getattr(moduleObj, module)    # returns the module object "modules.update"
moduleClass = getattr(submodule, module)  # returns the class object "update"
    # this works because the class has the same name as the file it contains it.
    # if the names are different then you have to change that accordingly 
name_attr = getattr(moduleClass, command) # returns the attribute with the name =command

to see what you got add some prints:

print moduleObj 
print submodule 
print moduleClass
print name_attr

you can call your script now with:

python app.py update name 

and the output will look like:

<module 'modules' from '<path>/modules/__init__.pyc'>
<module 'modules.update' from '<path>/modules/update.pyc'>
<class 'modules.update.update'>
update_name

I think now you can manipulate the code however you want to get any object you like, or even pass them as arguments like update and name earlier.

Edit:

you can not call unbounded method, you need to create an instance of that class and then call the method for that instance, that's what the error is saying. so you need to do something like in the following example:

import sys

arguments = sys.argv
module = arguments[1]
command = arguments[2]
moduleObj = __import__(module)            # returns module "modules"
submodule = getattr(moduleObj, module)    # returns the module "modules.update"
localInstance = submodule.update()           # create instance of class 'update'
localInstance.test()                         # call instance method

and this will print:

test complete!

PS:

It is not encouraged to use some words for your method names or variables like system or all, better change them.

hope this helps.

OTHER TIPS

Regarding the updated question:

The error in your updated code is due to the fact that you need to make an instance of the class before calling its method:

result = getattr(moduleClass(), command)

(Note the parentheses).


I think the error originates here:

moduleObj = __import__('modules.'+module)

When you call __import__ without the fromlist parameter, it returns the left-mode module, not the right-most module. To be clearer, let me give an example:

In [3]: __import__('xml.dom')
Out[3]: <module 'xml' from '/usr/lib/python2.7/xml/__init__.pyc'>

Notice the xml module is returned, not the xml.dom module! Even though xml.dom is not returned, it has been imported. You can access xml.dom through the sys.modules dict:

In [11]: sys.modules['xml.dom']
Out[11]: <module 'xml.dom' from '/usr/lib/python2.7/xml/dom/__init__.pyc'>

To get __import__ to return the xml.dom module, you need to use the fromlist parameter:

In [4]: __import__('xml.dom', fromlist=[''])
Out[4]: <module 'xml.dom' from '/usr/lib/python2.7/xml/dom/__init__.pyc'>

More information on why __import__ behaves this way can be found here.

So here are 3 different ways to fix your code:

  • Use fromlist:

    moduleObj = __import__('modules.'+module, fromlist=[''])
    

    (It doesn't matter what you assign to fromlist, it just has to be a non-empty list of strings.)

  • Use sys.modules:

    name = 'modules.'+module
    __import__(name)
    moduleObj = sys.modules[name]
    
  • Alternatively, use importlib.import_module:

    import importlib
    moduleObj = importlib.import_module('modules.'+module)
    

Of these three, I think the third is the most readable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top