Перетворити рядок в об’єкт класу 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.