Перетворити рядок в об’єкт класу Python?


187

Враховуючи рядок як введення користувачем функції Python, я хотів би вийти з нього об’єкт класу, якщо в поточно визначеному просторі імен є клас з цим іменем. По суті, я хочу, щоб реалізація функції, яка дала такий результат:

class Foo:
    pass

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

Чи це взагалі можливо?

python 

2
Тут є кілька корисних відповідей, але я знайшов відповідь на це питання особливо корисною: stackoverflow.com/questions/452969/…
Том

Відповіді:


115

Попередження : eval()може використовуватися для виконання довільного коду Python. Ніколи не слід використовувати eval()з ненадійними струнами. (Див. Безпека Python's eval () на недовірених рядках? )

Це здається найпростішим.

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

30
Нехай розгалуження використання eval. Ви краще переконайтеся, що рядок, який ви передали, не від користувача.
Розгон

18
Використання eval () залишає двері відкритими для довільного виконання коду, задля безпеки цього слід уникати.
m.kocikowski

54
Евал не залишає дверей відкритими ні перед чим. Якщо у вас є злісні, злі користувачі, які можуть злісно та злісно передавати погані значення на eval, вони можуть просто відредагувати джерело python. Оскільки вони можуть просто відредагувати джерело пітона, двері є, були і завжди будуть відчинені.
S.Lott

34
Якщо тільки програма, про яку йде мова, працює на сервері.
Vatsu1

12
@ s-lott Вибачте, але я думаю, що ви тут даєте дуже погані поради. Подумайте: Коли ваш комп’ютер запропонує ввести пароль, злий, зловмисний користувач може так само змінити відповідний виконуваний файл або бібліотеку і обійти цю перевірку. Тому можна стверджувати, що безглуздо додавати перевірки пароля в першу чергу. Або це?
Даніель Ск

148

Це може спрацювати:

import sys

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

Що буде, якщо класу не існує?
riza

7
Він працюватиме лише для класу, визначеного у поточному модулі
luc

1
Дійсно приємне рішення: тут ви використовуєте не імпорт, а більш загальний модуль sys.
Стефано Фальсетто

Чи відрізнятиметься це def str_to_class(str): return vars()[str]?
Арн

113

Ви можете зробити щось на кшталт:

globals()[class_name]

6
Мені це подобається краще, ніж рішення 'eval', тому що (1) це так само просто, і (2) він не залишає вас відкритими для довільного виконання коду.
mjumbewu

2
але ви використовуєте globals()... ще одну річ, якої слід уникати
Грег,

5
@Greg Можливо, але globals(), напевно, набагато менше, ніж eval, і насправді не гірше, ніж sys.modules[__name__]AFAIK.
Лоранс Гонсальв

2
Так, я бачу вашу думку, тому що ви лише хапаєтеся від нього, нічого не встановлюючи
Грег,

Що робити, якщо змінна використовується в класі, і кілька екземплярів для цього класу були зроблені одночасно. Чи спричинить це проблему, оскільки це глобальна змінна.
Алок Кумар Сінгх

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)

Це точно обробляє як класи старого, так і нового стилю.


Вам не потрібні типи.TypeType; це просто псевдонім для вбудованого "типу".
Гленн Мейнард

1
Так, я знаю, але я вважаю, що це просто зрозуміліше читати. Я думаю, що це просто зводиться до уподобань.
Еван Фосмарк

17

Я подивився, як джанго справляється з цим

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



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.
riza

1
Використовуйте речовину (х, тип).
Гленн Мейнард

3

З точки зору довільного виконання коду або небажаних користувачем переданих імен, у вас може бути список прийнятних імен функцій / класів, і якщо вхід відповідає одному зі списку, він буде eval'd.

PS: Я знаю .... якось пізно .... але це для всіх, хто натрапляє на це в майбутньому.


9
Якщо ви збираєтесь підтримувати список, натомість може підтримувати диктант від імені до класу. Тоді немає необхідності в eval ().
Fasaxc

3

Використання importlib працювало найкраще для мене.

import importlib

importlib.import_module('accounting.views') 

Тут використовується нотація рядкових точок для модуля python, який ви бажаєте імпортувати.


3

Якщо ви дійсно хочете отримати класи, які ви робите за допомогою рядка, вам слід зберігати (або правильно сформулювати текст) посилання ) на них у словнику. Зрештою, це також дозволить назвати ваші класи на більш високому рівні та уникнути викриття небажаних класів.

Приклад, з гри, де класи акторів визначені в Python, і ви хочете уникати інших загальних класів, до яких можна дістатися користувачем.

Інший підхід (як у прикладі нижче) мав би скласти цілий новий клас, який дотримується 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щоб ви ніколи не втрачали всі класи, які ви робили: ^)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.