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

167 python

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

class Foo:
    pass

str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>
Run Code Online (Sandbox Code Playgroud)

这是可能吗?

six*_*ear 137

这可能有效:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)
Run Code Online (Sandbox Code Playgroud)

  • 它仅适用于当前模块中定义的类 (4认同)

Lau*_*ves 107

你可以这样做:

globals()[class_name]
Run Code Online (Sandbox Code Playgroud)

  • 我比'eval'解决方案更喜欢这个,因为(1)它同样简单,并且(2)它不会让你对任意代码执行开放. (6认同)
  • @Greg也许吧,但是`globals()`可能远不如`eval`那么糟糕,而且实际上并不比`sys.modules [__ name __]`AFAIK差. (4认同)
  • 但是您使用的是“globals()”……另一件应该避免的事情 (3认同)
  • 是的,我明白了你的观点,因为你只是抓住它而不是设置任何东西 (2认同)

S.L*_*ott 102

警告:eval()可用于执行任意Python代码.你永远eval()不应该使用不受信任的字符串.(请参阅Python的eval()对不受信任的字符串的安全性?)

这似乎最简单.

>>> class Foo(object):
...     pass
... 
>>> eval("Foo")
<class '__main__.Foo'>
Run Code Online (Sandbox Code Playgroud)

  • Eval不会对任何东西敞开大门.如果你有恶意,邪恶的用户可能恶意和恶意地将坏值传递给eval,他们只能编辑python源.因为他们可以只编辑python源,所以门是,现在,并且始终是开放的. (52认同)
  • 除非有问题的程序在服务器上运行. (33认同)
  • 要注意使用eval的分支.您最好确保传入的字符串不是来自用户. (29认同)
  • 使用 eval() 为任意代码执行打开了大门,为了安全起见,应该避免它。 (18认同)
  • @ s-lott我很抱歉,但我认为你在这里给出了非常糟糕的建议.请考虑:当您的计算机提示您输入密码时,恶意的恶意用户也可以修改相应的可执行文件或库并绕过此检查.因此,有人可能会说,首先添加密码检查是没有意义的.或者是吗? (8认同)
  • 如果string不是来自用户,则不会有任何问题. (3认同)
  • @Daerdemandt 我想我应该更清楚地说明我的观点。让我再试一次:有两种情况 - 1) 用户/程序能够直接编辑可执行文件/库(python 代码、机器代码等)。- 2) 一个可执行文件/库,它将接受来自用户/程序的输入,然后按原样盲目地执行该输入(python 代码、机器代码等)。S.Lott 主张案例二,并且似乎不认为与案例一有区别。 (3认同)
  • 9年没有人注意到他拼错了? (2认同)
  • 只是根本不这样做。eval() 是一种糟糕的形式,与不安全无关。忘记 eval() 甚至存在,并使用 getattr() 按名称字符串查找对象 (2认同)

m.k*_*ski 91

你想要这个类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
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

使用:

loaded_class = class_for_name('foo.bar', 'Baz')
Run Code Online (Sandbox Code Playgroud)


Eva*_*ark 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)
Run Code Online (Sandbox Code Playgroud)

这准确地处理了旧式和新式类.


eug*_*ene 13

我看过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])
Run Code Online (Sandbox Code Playgroud)

你可以像使用它一样 import_string("module_path.to.all.the.way.to.your_class")

  • 这是一个很好的答案。这是[具有当前实现的最新 Django 文档的链接](https://docs.djangoproject.com/en/dev/_modules/django/utils/module_loading/)。 (2认同)

oll*_*lyc 5

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

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>
Run Code Online (Sandbox Code Playgroud)

  • 在Python 3中,不再有ClassType。 (2认同)

Gus*_*046 5

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

例如,来自一个在 Python 中定义演员类的游戏,并且您希望避免用户输入访问其他通用类。

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

  • 允许创建多个类持有者以便于组织(例如,一个用于演员类,另一个用于声音类型);
  • 使对持有者和所持有的类的修改更容易;
  • 您可以使用类方法将类添加到字典中。(虽然下面的抽象并不是真正必要的,它只是为了...... “插图”)。

例子:

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!"
Run Code Online (Sandbox Code Playgroud)

这让我返回:

<__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!
Run Code Online (Sandbox Code Playgroud)

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

更新:也可以使用装饰器作为速记。

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
Run Code Online (Sandbox Code Playgroud)

  • 哎呀,我写了一个答案,然后看到了这个。同意 - 我认为这是最好的方法。简单,只需使用最少代码/附加的语言。 (2认同)