0

I have an issue where I need to instantiate a class, but I'm not sure what the most optimal way to go about this is.

Imagine the following code:

# loadstuff.py

class LoadSomething:
    def load(self, filename):
        compiled = compile(open(filename).read(), filename, 'exec')

        # The part I am stuck on
        # How do I get the class name, and how do I instantiate it? 
        # This is assuming the file name is the same as the class name, only starting with capital letter, and minus file extension
        module = compiled.  # instantiate class 'Printstuff'

        return module 

loader = LoadSomething()
module = loader.load("printstuff.py")
module.printSomething()  # Should print "Something!"

# printstuff.py

class Printstuff:
    def printSomething(self):
        print("Something!")

I think the code (and thus the question) speaks mostly for itself, how do I return and instantiate a new class with compile(..) so that I can call its methods?

Zarthus
  • 466
  • 6
  • 18
  • Are you certain you should be using `compile()`? As opposed to, say, `__import__` or `imp.load_module()`? – al45tair Jul 22 '14 at 13:31
  • @alastair I'm not certain, I'm looking for the best way to load a class using it's source code rather than import though. (I don't want people to use `from modules import printstuff` `printstuff.Printstuff().printsomething()`) because the system should load these dynamically. – Zarthus Jul 22 '14 at 13:41
  • You don't have to use `exec`, `compile` or such to *dynamically* import a module, that's what the `imp` module is for (cf http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path). – bruno desthuilliers Jul 22 '14 at 14:06

2 Answers2

1

There is no need to manually compile. Use importlib.import_module() to dynamically import the module.

You can then use inspect.getmembers() together with the inspect.isclass() predicate to find all classes, including any that might match the module name:

import importlib
import inspect

class LoadSomething:
    def load(self, modulename):
        module = importlib.import_module(modulename)
        modname = modulename.rpartition('.')[-1]  # support packages too
        for clsname, cls in inspect.getmembers(module, inspect.isclass):
            if clsname == modname.title():
                return cls()

This returns an instance of the first class that has the same name as the module, titlecased.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That only allows you to load modules that are in Python's path. Also, it uses the module system, which is undesirable in some cases because e.g. it means you can't load modules that have the same name. – al45tair Jul 22 '14 at 14:27
  • Hello, I'm having trouble finding documentation on what `importlib(modulename)` does, what are it's parameters, what exactly does it call? Could you elaborate on that perhaps? – Zarthus Jul 22 '14 at 14:37
  • @alastair: You can always complicate matters more; the OP should try to work *with* the import functionality, not against it. Putting all plugins into a namespace would go a long way towards that. The Python 3 `importlib` contains plenty of functionality to extend on that if so desired (including any number of ways that prevent name clashes with 'regular' modules). – Martijn Pieters Jul 22 '14 at 14:37
  • 1
    @Zarthus: I made an error in the answer; the `import_module()` function name was omitted. – Martijn Pieters Jul 22 '14 at 14:38
0

The fundamental problem is that the code object (the thing you've used compile() to produce) can contain any Python code — e.g. it could define multiple classes. As a result, you're going to have to specify how the file you're loading is supposed to indicate which class to use.

One option, for instance, would be to require it to define a class with a well-known name. For instance:

codeobj = compile(open(filename).read(), filename, 'exec')
globs = {}
locs = {}
exec(codeobj, globs, locs)
cls = globs['Foobar']
obj = cls()

You could simplify this quite a bit by using execfile() instead of compile() and exec():

globals = {}
locals = {}
execfile(filename, globs, locs)
cls = globs['Foobar']
obj = cls()

It's quite easy to come up with more complex schemes as well.

FWIW, the reason to do this rather than use imp, importlib or __import__ is that the import-based options use the module system. If this is for loading a plug-in, you probably don't want that, so execfile() et al are more appropriate.

al45tair
  • 4,405
  • 23
  • 30
  • 1
    Please, do **not** advise peoples to use `exec` or `execfile` when there are better solutions. If you don't understand why, read this: http://lucumr.pocoo.org/2011/2/1/exec-in-python/ . In the above case, Python has features to let you dynamically import a module. – bruno desthuilliers Jul 22 '14 at 14:08
  • @brunodesthuilliers Disagree. This isn't about importing a module, it's about loading something that explicitly *is not* a module. In that case, `imp` is **not** a better solution IMO (not least because you'd have to generate a unique module name each time, which breaks pickle just as much as using `execfile()` does in the first place). – al45tair Jul 22 '14 at 14:14
  • The code provided worked, I'm going to dig deeper into the other answer and see what is best for me. As far as this goes however, I would consider it a valid answer. I'll let this question unfold a bit more and educate myself some more, then I'll see what I'll roll with. Nevertheless, it works, and it seems to do as I ask, your contributions are appreciated. :) – Zarthus Jul 22 '14 at 14:43