Преобразовать строку в объект класса Python?
Учитывая строку в качестве пользовательского ввода в функцию Python, я хотел бы извлечь из нее объект класса, если в текущем определенном пространстве имен есть класс с таким именем. По сути, я хочу реализацию для функции, которая будет давать такой результат:
class Foo:
pass
str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>
Это вообще возможно?
11 ответов
Предупреждение:
eval()
может быть использован для выполнения произвольного кода Python. Вы никогда не должны использоватьeval()
с ненадежными строками. (См. Безопасность Python eval() на ненадежных строках?)
Это кажется самым простым.
>>> class Foo(object):
... pass
...
>>> eval("Foo")
<class '__main__.Foo'>
Это может сработать:
import sys
def str_to_class(classname):
return getattr(sys.modules[__name__], classname)
Вы хотите класс 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')
Я смотрел на то, как 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")
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)
Это точно обрабатывает классы как старого, так и нового стиля.
Если вы действительно хотите извлекать классы, созданные вами, со строкой, вы должны хранить (или правильно сформулировать, ссылаться) их в словаре. В конце концов, это также позволит назвать ваши классы на более высоком уровне и избежать выявления нежелательных классов.
Пример из игры, в которой классы актеров определены в 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
так что вы никогда не потеряете все классы, которые вы сделали:^)
Да, ты можешь это сделать. Предполагая, что ваши классы существуют в глобальном пространстве имен, что-то вроде этого сделает это:
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>
С точки зрения выполнения произвольного кода или нежелательных имен, переданных пользователем, у вас может быть список допустимых имен функций / классов, и, если входные данные соответствуют одному в списке, это eval'd.
PS: я знаю... немного поздно... но это для всех, кто столкнется с этим в будущем.
Использование importlib работало лучше для меня.
import importlib
importlib.import_module('accounting.views')
При этом используется нотация с точкой для модуля Python, который вы хотите импортировать.
Я не понимаю, почему это не сработает. Это не так конкретно, как ответы всех остальных, но если вы знаете название своего класса, этого достаточно.
def str_to_class(name):
if name == "Foo":
return Foo
elif name == "Bar":
return Bar