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


187

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

class Foo:
    pass

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

这是可能吗?

python 

2
这里有一些有用的答案,但是我发现这个问题的答案特别有用:stackoverflow.com/questions/452969/…–
汤姆(Tom)

Answers:


115

警告eval()可用于执行任意Python代码。您永远不要使用eval()不受信任的字符串。(请参阅关于不可信字符串的Python eval()的安全性?

这似乎最简单。

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

30
当心使用评估的后果。您最好确保传入的字符串不是来自用户。
2013年

18
使用eval()可以为任意代码执行打开大门,出于安全考虑,应避免这样做。
m.kocikowski

54
埃瓦尔并没有敞开大门。如果您有恶意,邪恶的用户,他们可能会恶意地将错误的值传递给eval,则他们只需编辑python源即可。由于他们只能编辑python源代码,因此门过去,过去和将来始终是打开的。
S.Lott

34
除非相关程序正在服务器上运行。
Vatsu1

12
@ s-lott很抱歉,但是我认为您在这里提出的建议很糟糕。考虑:当您的计算机提示您输入密码时,恶意的恶意用户也可能会修改相应的可执行文件或库并绕过此检查。因此,可能有人认为首先添加密码检查是没有意义的。还是?
Daniel Sk

148

这可以工作:

import sys

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

如果该类不存在,将会发生什么?

7
它仅适用于当前模块中定义的类
luc

1
真正好的解决方案:在这里,您没有使用import,而是使用 了更通用的sys模块。
Stefano Falsetto

这和有什么不同def str_to_class(str): return vars()[str]吗?
Arne

113

您可以执行以下操作:

globals()[class_name]

6
我比“评估”解决方案更好,因为(1)一样简单,并且(2)不会让您随意执行代码。
mjumbewu 2012年

2
但是您正在使用globals()...应该避免的另一件事
Greg

5
@Greg也许,但globals()可以说远没有坏eval,而且也没有比sys.modules[__name__]AFAIK 更糟。
劳伦斯·贡萨尔维斯

2
是的,我明白您的意思,因为您只是从中抓住而没有设置任何内容
Greg

如果变量在一个类中使用并且该类的多个实例是同时创建的,该怎么办。它会引起问题吗,因为这是一个全局变量。
Alok Kumar Singh,

98

您需要类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 <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')

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)

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


您不需要type.TypeType; 它只是内置“类型”的别名。
格伦·梅纳德

1
是的,我知道,但是我觉得阅读起来更清晰。我想这只是归结为偏好。
Evan Fosmark,2009年

17

我看过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")


这是一个很好的答案。这是最新实现的Django文档的链接
格雷格·卡莱卡

3

是的,您可以这样做。假设您的类存在于全局名称空间中,则将执行以下操作:

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>

1
在Python 3中,不再有ClassType。
萨2009年

1
使用isinstance(x,type)。
格伦·梅纳德

3

就任意代码执行或不希望的用户传递名称而言,您可以具有可接受的函数/类名称的列表,如果输入与列表中的一个匹配,则进行评估。

PS:我知道。。。有点迟了。。。。。


9
如果您要维护列表,则最好维护一个从名称到类的字典。这样就不需要eval()了。
Fasaxc


3

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

例如,在一个游戏中,用Python定义了actor类,并且您希望避免用户输入会到达其他常规类。

另一种方法(如下面的示例所示)将创建一个包含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这样您就不会丢失所做的所有类:^)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.