13

Trying to instantiate a class based on a string value and... failing. The parser object below is a dict, in the example let's say we have one called foo and here parser['name'] is 'foo':

obj = parser['name']()

Fails, yielding TypeError: 'str' object is not callable. But, since I have:

class foo:
    def __init__(self():
        print 'Hello'

And if I do obj = foo() it works fine and creates the correct object. Also, calling obj = type(parser['name'])() doesn't work.

How to resolve this? Update: I don't really want to use a mapping system: the names of these classes are defined INI files, and parsed that way, so they will be strings..

8 Answers 8

17
classmap = {
  'foo': foo
}

obj = classmap[parser['name']]()
Sign up to request clarification or add additional context in comments.

4 Comments

So if you use the factory pattern, somewhere in your code you must map all possible classes that can be returned?
Just use a decorator to enumerate them.
Why is this better than doing an if statement for every possible value of a string
@Prinsig memory consumption ;)
16

As answered in:
Python dynamic class names

There is an easier way to do this if you know which module the classes are defined in, for example:

getattr(my_module, my_class_name)()

Comments

8

Don't use strings:

parser = {}

class foo:
    pass


parser['foo'] = foo

obj = parser['foo']()

Comments

6

You can use a metaclass that stores a dict of known classes:

# a metaclass
class Registry(type):
    # store all the types we know
    registered = {}
    def __new__(cls, name, bases, attrs):
        # create the new type
        newtype = super(Registry, cls).__new__(cls, name, bases, attrs)
        # store it
        cls.registered[name] = newtype
        return newtype

    @classmethod
    def class_by_name(cls, name):
        # get a class from the registerd classes
        return cls.registered[name]


# arbitrary base class for every class that should be in the Register
class Registered(object):
    __metaclass__ = Registry

# some classes
class Foo(Registered):
    pass

class Bar(Foo):
    pass

 # get the class object: 
print Registry.class_by_name('Foo') # <class '__main__.Foo'>
# it can be instanciated too ofc:
print Registry.class_by_name('Bar')() # <__main__.Bar object at 0x01F0F9F0>

But not everyone understands metaclasses, so you might want to avoid them to prevent any confusion. They can be useful for stuff like this, but as you can see from the other answers, there are plenty other ways to do it.

Comments

5

The type(name, bases, dict) built-in function is the correct way to dynamically construct classes--especially when given strings for class names. See the documentation here: http://docs.python.org/library/functions.html#type

In you particular example, it might look like this:

>>> def init(self):
...     print 'Hello'
...
>>> Foo = type('Foo', (object,), {'__init__': init})
>>> foo = Foo()
Hello

4 Comments

the problem isn't to dynamically construct classes but to dynamically instantiate them.
Then obj = type('Foo', (object,), {'__init__': init})() ought to do the trick. That prints 'Hello' and make obj an instance of the class.
Doing it this way creates a new class for each instance. It also takes what's a nice, semantic class definition and breaks it up into functions spread around the file. Also, how do you map different functions to their respective classes. What if there was a class 'Bar' with an init that prints 'Goodbye'?
+1. Although this isn't properly an answer to the question, it's what I was looking to do. (That is, the answer itself, not the silly follow-up comment.)
2

You could use inspect to create the class map:

def get_classes():
    classes = {}
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj):
            classes[name] = obj
    return classes

Then instantiate a class

>>> classes = get_classes()
>>> c = classes['ClassName']
>>> c
<class ClassName...>

Mostly nicked from How can I get a list of all classes within current module in Python?

Comments

-1

In response to your update: The only way to do this is with a mapping system. If you don't want to use an explicit mapping system, then you can use one of Python's built-in mapping systems, though it's much less nice and explicit in my opinion.

obj = globals()[parser['name']]() 

will access the global object of with name parser['name'] == 'foo'. If this happens to be a class (or a class that you actually want instantiated based on user input), then you should be good to go. Otherwise, you will have to build logic around it to whitelist the classes that you actually want.

If the classes are coming from a module, then you can use that module's __dict__ attribute to the same effect.

obj = somemodule.__dict__[parser['name']]()

The same caveats apply to this situation as the previous one. It's really better to just use an explicit mapping

Comments

-4

I would use a map of class name to class object like everyone else is saying. You can initialize it using statements like parser[foo.__name__] = foo. If you do not want to use a mapping object, then you will have to fall back to using the eval function like the following:

>>> class foo:
...   pass
...
>>> klass_name = 'foo'
>>> klass_inst = eval(klass_name)
>>> klass_inst
<class __main__.foo at 0x1004b2b90>
>>> inst = klass_inst()
>>> inst
<__main__.foo instance at 0x1004d2680>
>>>

Of course if you want to use classes that are embedded within a package, then the package will have to be imported before you do the eval. You really should build a mapping object so that you can limit the classes that can be accessed using this code.

4 Comments

There's no need to even mentioning eval when there is a solution a thousand times cleaner and safer available. (I didn't downvote though)
Technically, when using eval, you are using a mapping system: globals().
@delnan: that is why I said that eval is the fall back if you don't want to use an explicit mapping object. I wanted to mention it since some people actually prefer using it. Especially people that come from Perl and Shell. Thanks for restraining the downvote though ;)
@aaronasterling: I should have said explicit mapping since most people don't think of variable lookup within an interpreter as a mapping even though it is.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.