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

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

class Foo:
pass


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

这有可能吗?

328648 次浏览

你可以这样做:

globals()[class_name]

这是可行的:

import sys


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

是的,你可以做到。假设你的类存在于全局命名空间中,如下所示:

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

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

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

在任意代码执行方面,或者不希望用户传递的名称,您可以有一个可接受的函数/类名称列表,如果输入与列表中的一个匹配,则对其进行计算。

PS:我知道....有点晚了…但这是为将来偶然发现这个的人准备的。

使用importlib最适合我。

import importlib


importlib.import_module('accounting.views')

这将使用字符串点表示法作为你想导入的python模块。

我看了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")那样使用它

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

例如,在一个游戏中,actor类是用Python定义的,而你希望避免用户输入到达其他通用类。

另一种方法(如下面的例子)将创建一个全新的类,其中包含上面的dict。这将:

  • 允许多个类持有者更容易组织(比如,一个用于actor类,另一个用于声音类型);
  • 使持有者和被持有的类的修改更容易;
  • 您可以使用类方法向字典中添加类。(尽管下面的抽象并不是真正必要的,它只是为了……“illustration")。

例子:

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

这让我想到:

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

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

更新:也可以使用decorator作为简写。

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