Convert string to Python class object?

Given a string as user input to a Python function, I'd like to get a class object out of it if there's a class with that name in the currently defined namespace. Essentially, I want the implementation for a function which will produce this kind of result:

class Foo: pass
str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>

Is this, at all, possible?

1

10 Answers

This could work:

import sys
def str_to_class(classname): return getattr(sys.modules[__name__], classname)
5

Warning: eval() can be used to execute arbitrary Python code. You should never use eval() with untrusted strings. (See Security of Python's eval() on untrusted strings?)

This seems simplest.

>>> class Foo(object):
... pass
...
>>> eval("Foo")
<class '__main__.Foo'>
12

You could do something like:

globals()[class_name]
6

You want the class Baz, which lives in module foo.bar. With Python 2.7, you want to use importlib.import_module(), as this will make transitioning to Python 3 easier:

import importlib
def class_for_name(module_name, class_name): # load the module, will raise ImportError if module cannot be loaded m = importlib.import_module(module_name) # get the class, will raise AttributeError if class cannot be found c = getattr(m, class_name) return c

With Python < 2.7:

def class_for_name(module_name, class_name): # load the module, will raise ImportError if module cannot be loaded m = __import__(module_name, globals(), locals(), class_name) # get the class, will raise AttributeError if class cannot be found c = getattr(m, class_name) return c

Use:

loaded_class = class_for_name('foo.bar', 'Baz')
0

I've looked at how django handles this

django.utils.module_loading has this

def import_string(dotted_path): """ Import a dotted module path and return the attribute/class designated by the last name in the path. Raise ImportError if the import failed. """ try: module_path, class_name = dotted_path.rsplit('.', 1) except ValueError: msg = "%s doesn't look like a module path" % dotted_path six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) module = import_module(module_path) try: return getattr(module, class_name) except AttributeError: msg = 'Module "%s" does not define a "%s" attribute/class' % ( module_path, class_name) six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

You can use it like import_string("module_path.to.all.the.way.to.your_class")

1
import sys
import types
def str_to_class(field): try: identifier = getattr(sys.modules[__name__], field) except AttributeError: raise NameError("%s doesn't exist." % field) if isinstance(identifier, (types.ClassType, types.TypeType)): return identifier raise TypeError("%s is not a class." % field)

This accurately handles both old-style and new-style classes.

2

If you really want to retrieve classes you make with a string, you should store (or properly worded, reference) them in a dictionary. After all, that'll also allow to name your classes in a higher level and avoid exposing unwanted classes.

Example, from a game where actor classes are defined in Python and you want to avoid other general classes to be reached by user input.

Another approach (like in the example below) would to make an entire new class, that holds the dict above. This would:

  • Allow multiple class holders to be made for easier organization (like, one for actor classes and another for types of sound);
  • Make modifications to both the holder and the classes being held easier;
  • And you can use class methods to add classes to the dict. (Although the abstraction below isn't really necessary, it is merely for... "illustration").

Example:

class ClassHolder: def __init__(self): self.classes = {} def add_class(self, c): self.classes[c.__name__] = c def __getitem__(self, n): return self.classes[n]
class Foo: def __init__(self): self.a = 0 def bar(self): return self.a + 1
class Spam(Foo): def __init__(self): self.a = 2 def bar(self): return self.a + 4
class SomethingDifferent: def __init__(self): self.a = "Hello" def add_world(self): self.a += " World" def add_word(self, w): self.a += " " + w def finish(self): self.a += "!" return self.a
aclasses = ClassHolder()
dclasses = ClassHolder()
aclasses.add_class(Foo)
aclasses.add_class(Spam)
dclasses.add_class(SomethingDifferent)
print aclasses
print dclasses
print "======="
print "o"
print aclasses["Foo"]
print aclasses["Spam"]
print "o"
print dclasses["SomethingDifferent"]
print "======="
g = dclasses["SomethingDifferent"]()
g.add_world()
print g.finish()
print "======="
s = []
s.append(aclasses["Foo"]())
s.append(aclasses["Spam"]())
for a in s: print a.a print a.bar() print "--"
print "Done experiment!"

This returns me:

<__main__.ClassHolder object at 0x02D9EEF0>
<__main__.ClassHolder object at 0x02D9EF30>
=======
o
<class '__main__.Foo'>
<class '__main__.Spam'>
o
<class '__main__.SomethingDifferent'>
=======
Hello World!
=======
0
1
--
2
6
--
Done experiment!

Another fun experiment to do with those is to add a method that pickles the ClassHolder so you never lose all the classes you did :^)

UPDATE: It is also possible to use a decorator as a shorthand.

class ClassHolder: def __init__(self): self.classes = {} def add_class(self, c): self.classes[c.__name__] = c # -- the decorator def held(self, c): self.add_class(c) # Decorators have to return the function/class passed (or a modified variant thereof), however I'd rather do this separately than retroactively change add_class, so. # "held" is more succint, anyway. return c def __getitem__(self, n): return self.classes[n]
food_types = ClassHolder()
@food_types.held
class bacon: taste = "salty"
@food_types.held
class chocolate: taste = "sweet"
@food_types.held
class tee: taste = "bitter" # coffee, ftw ;)
@food_types.held
class lemon: taste = "sour"
print(food_types['bacon'].taste) # No manual add_class needed! :D
2

Yes, you can do this. Assuming your classes exist in the global namespace, something like this will do it:

import types
class Foo: pass
def str_to_class(s): if s in globals() and isinstance(globals()[s], types.ClassType): return globals()[s] return None
str_to_class('Foo')
==> <class __main__.Foo at 0x340808cc>
2

In terms of arbitrary code execution, or undesired user passed names, you could have a list of acceptable function/class names, and if the input matches one in the list, it is eval'd.

PS: I know....kinda late....but it's for anyone else who stumbles across this in the future.

1

Using importlib worked the best for me.

import importlib
importlib.import_module('accounting.views') 

This uses string dot notation for the python module that you want to import.

1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like