将字符串转换为Python类对象?

时间:2009-07-24 07:01:24

标签: python

给定一个字符串作为Python函数的用户输入,如果在当前定义的命名空间中有一个具有该名称的类,我想从中获取一个类对象。基本上,我想要一个能产生这种结果的函数的实现:

class Foo:
    pass

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

这可能吗?

11 个答案:

答案 0 :(得分:127)

这可行:

import sys

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

答案 1 :(得分:99)

您可以执行以下操作:

globals()[class_name]

答案 2 :(得分:96)

  

警告eval()可用于执行任意Python代码。你应该 从不 使用eval()不受信任的字符串。 (参见 Security of Python's eval() on untrusted strings?

这似乎最简单。

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

答案 3 :(得分:84)

您希望课程Baz位于模块foo.bar中。使用Python 2.7, 您想使用importlib.import_module(),因为这样可以更轻松地转换到Python 3:

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

使用Python&lt; 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

使用:

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

答案 4 :(得分:19)

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)

这可以准确地处理旧式和新式类。

答案 5 :(得分:12)

我看过django如何处理这个

django.utils.module_loading有这个

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

您可以像import_string("module_path.to.all.the.way.to.your_class")

一样使用它

答案 6 :(得分:3)

就任意代码执行或不需要的用户传递的名称而言,您可以拥有一个可接受的函数/类名列表,如果输入与列表中的一个匹配,则它是eval'd。

PS:我知道......有点迟了......但是对于其他未来偶然发现的人来说也是如此。

答案 7 :(得分:2)

是的,你可以这样做。假设您的类存在于全局命名空间中,这样的事情就会这样做:

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>

答案 8 :(得分:2)

使用importlib对我来说效果最好。

import importlib

importlib.import_module('accounting.views') 

这为您要导入的python模块使用 string dot notation

答案 9 :(得分:2)

如果你真的想要检索用字符串创建的类,你应该在字典中存储(或者措辞正确,引用)它们。毕竟,这也允许在更高级别命名您的类,并避免暴露不需要的类。

示例,来自游戏,其中actor类是在Python中定义的,并且您希望避免用户输入到达其他常规类。

另一种方法(如下例所示)将创建一个包含上面dict的全新类。这会:

  • 允许多个类持有者更容易组织(例如,一个用于演员类,另一个用于声音类型);
  • 对持有人和持有的课程进行修改更容易;
  • 您可以使用类方法向dict添加类。 (虽然下面的抽象并不是真的有必要,但仅仅是为了...... “插图”)。

示例:

class ClassHolder(object):
    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(object):
    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(object):
    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!"

这让我回报:

<__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!

另一个有趣的实验是添加一个方法来挑选ClassHolder,这样你就不会丢失你所做的所有类:^)

答案 10 :(得分:0)

我不明白为什么这行不通。它不像其他人的答案那么具体,但如果你知道你班级的名字就足够了。

def str_to_class(name):
    if name == "Foo":
        return Foo
    elif name == "Bar":
        return Bar