242

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?

  • 2
    There are some useful answers here, but I found the answer to this question particularly useful: http://stackoverflow.com/questions/452969/does-python-have-an-equivalent-to-java-class-forname – Tom Oct 24 '13 at 00:45

10 Answers10

200

This could work:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)
Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88
sixthgear
  • 6,390
  • 2
  • 22
  • 17
151

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'>
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 34
    Beaware the ramification of using eval. You better make sure the string you passed in is not from user. – Overclocked Jul 18 '13 at 13:32
  • 19
    Using eval() leaves the door open for arbitrary code execution, for security's sake it should be avoided. – m.kocikowski Aug 20 '13 at 19:14
  • 64
    Eval does not leave the door open to anything. If you have malicious, evil users who might maliciously and evilly pass bad values to eval, they can just edit the python source. Since they can just edit the python source, the door is, was, and always will be open. – S.Lott Sep 19 '13 at 12:18
  • 39
    Unless the program in question is running on a server. – Vatsu1 Nov 22 '13 at 21:04
  • 16
    @s-lott I'm sorry, but I think you are giving very bad advice here. Consider: When your computer prompts you for a password, the evil, malicious user might just as well modify the corresponding executable or library and bypass this check. Therefore, one might argue that is is pointless to add password checks in the first place. Or is it? – Daniel Sk Oct 02 '15 at 09:45
  • 3
    If string is not from user there wouldn't be any problems. – pjsofts Jun 24 '16 at 08:54
  • 1
    @DanielSk, if *evil, malicious user might just as well modify the corresponding executable* then there's indeed little to no point in inconveniencing user with prompting for a password. To avoid this situation, you only allow *trusted* users to modify corresponding libraries. – Daerdemandt Aug 22 '16 at 16:46
  • 3
    @Daerdemandt I think I should have stated my point more clearly. Let me try again: There are two cases - 1) a user/program being able to edit the executable/library directly (python code, machine code, whatever). - 2) an executable/library that will accept input from a user/program and then blindly execute that input (python code, machine code, whatever) as is. S.Lott advocates case number two, and seemingly doesn't think there is a difference to case number one. – Daniel Sk Aug 26 '16 at 08:12
  • Yeah, that makes sense. However, *evil, malicious user might just as well modify the corresponding executable or library and bypass this check* is right in the domain of 1).\ In context of this answer, I suppose there should be some syntax validation that only allows stuff that *could* be a class name - we don't need all power of `eval`, just a tiny subset. – Daerdemandt Aug 26 '16 at 13:08
  • 1
    @S.Lott the correct thing to say would be that there is a security risk when *(a)* the thing being evaluated is taken from external program input (the user or a third-party server) and *(b)* this external input source has less privileges than the program being executed. If these conditions are true, using `eval` results in vulnerabilities. Could you please, for the sake of people without an education in information security, edit your answer to warm them about these two conditions? – Steve Dodier-Lazaro May 14 '17 at 10:53
  • 4
    9 years and nobody noticed he misspelled evil ? – Romain Vincent Mar 01 '18 at 18:46
  • 2
    Just don't do it at all. eval() is just terrible form, independent of being insecure. forget that eval() even exists, and use getattr() to look up objects by name strings – RBF06 Sep 19 '18 at 18:54
146

You could do something like:

globals()[class_name]
Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
  • 7
    I like this better than the 'eval' solution because (1) it's just as easy, and (2) it doesn't leave you open to arbitrary code execution. – mjumbewu May 11 '12 at 15:43
  • 3
    but you're using `globals()`... another thing that should be avoided – Greg Sep 12 '13 at 18:34
  • 7
    @Greg Perhaps, but `globals()` is arguably far less bad than `eval`, and not really any worse than `sys.modules[__name__]` AFAIK. – Laurence Gonsalves Sep 13 '13 at 22:18
  • 3
    Yeah I see your point, because you're only grabbing from it not setting anything – Greg Sep 14 '13 at 07:03
  • What if the variable is used in a class and multiple instances fo that class were made simultaneously at the same time. Will it cause problem, as this is a global variable. – Alok Kumar Singh Apr 09 '20 at 11:01
  • Basing on the request "if there's a class with that name in the currently defined namespace" I see this as the best approach, thanks: I'm using it as locals()[class_name]. Up to the developer to wrap it with an error trap. – LittleEaster Nov 20 '21 at 14:35
117

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')
m.kocikowski
  • 5,422
  • 2
  • 23
  • 9
41

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")

Brendan Quinn
  • 2,059
  • 1
  • 19
  • 22
eugene
  • 39,839
  • 68
  • 255
  • 489
  • 4
    This is a great answer. Here's [a link to the latest Django docs with the current implementation](https://docs.djangoproject.com/en/dev/_modules/django/utils/module_loading/). – Greg Kaleka Jan 24 '20 at 16:38
21
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.

Evan Fosmark
  • 98,895
  • 36
  • 105
  • 117
9

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
wallabra
  • 412
  • 8
  • 17
  • 2
    Gee I had written an answer proposing that, then saw this. Agreed - I think this is the best approach. SImple, just using the language with minimal code/additons. – logicOnAbstractions Oct 24 '20 at 20:04
  • Thank you @logicOnAbstractions, I am glad that you think so! I'm a little flattered. You inspired me to add a more updated example :D I have learned a lot since 2016. Most of my code back then was kind of ugly. This definitely stood out, though. Even if it's just a Stack Overflow answer! – wallabra Oct 26 '20 at 02:28
5

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>
ollyc
  • 5,589
  • 1
  • 17
  • 8
3

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.

JoryRFerrell
  • 33
  • 2
  • 16
  • 11
    If you're going to maintain the list, might as well maintain a dict from name to class instead. Then there's no need for eval(). – Fasaxc Dec 19 '13 at 00:48
3

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.

Aaron Lelevier
  • 19,850
  • 11
  • 76
  • 111
  • This is good to import a new module from a string. Question is to have a type from a string while that is already imported – LittleEaster Nov 20 '21 at 14:30