Konwertuj ciąg znaków na obiekt klasy Python?


187

Biorąc pod uwagę ciąg znaków wprowadzany przez użytkownika do funkcji Python, chciałbym wyciągnąć z niego obiekt klasy, jeśli istnieje klasa o tej nazwie w aktualnie zdefiniowanej przestrzeni nazw. Zasadniczo chcę implementacji funkcji, która da taki wynik:

class Foo:
    pass

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

Czy to w ogóle jest możliwe?

python 

2
Istnieje kilka przydatnych odpowiedzi tutaj, ale uważam, że odpowiedź na to pytanie jest szczególnie przydatna: stackoverflow.com/questions/452969/…
— Tom

Odpowiedzi:


115

Ostrzeżenie : eval()może być użyte do wykonania dowolnego kodu Pythona. Nigdy nie powinieneś używać eval()z niezaufanymi łańcuchami. (Zobacz Bezpieczeństwo eval () Pythona na niezaufanych ciągach? )

To wydaje się najprostsze.

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

30
Uważaj na konsekwencje używania eval. Lepiej upewnij się, że przekazany ciąg nie pochodzi od użytkownika.
— Overclocked

18
Użycie eval () pozostawia otwarte drzwi do wykonania dowolnego kodu, ze względów bezpieczeństwa należy tego unikać.
— m.kocikowski,

54
Eval nie pozostawia otwartych drzwi przed niczym. Jeśli masz złośliwych, złych użytkowników, którzy złośliwie i złośliwie przekazują złe wartości do ewaluacji, mogą po prostu edytować źródło Pythona. Ponieważ mogą po prostu edytować źródło Pythona, drzwi są, były i zawsze będą otwarte.
— S.Lott,

34
Chyba że dany program działa na serwerze.
— Vatsu1,

12
@ s-lott Przykro mi, ale myślę, że udzielasz tutaj bardzo złych rad. Zastanów się: kiedy komputer wyświetli monit o podanie hasła, zły, złośliwy użytkownik może równie dobrze zmodyfikować odpowiedni plik wykonywalny lub bibliotekę i pominąć tę kontrolę. Dlatego można argumentować, że dodawanie sprawdzania hasła nie ma sensu. Albo to jest?
— Daniel Sk

148

To może działać:

import sys

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

Co się stanie, jeśli klasa nie będzie istnieć?
— riza

7
Będzie działać tylko dla klasy zdefiniowanej w bieżącym module
— luc

1
Naprawdę fajne rozwiązanie: tutaj nie używasz importu, ale bardziej ogólny moduł sys.
— Stefano Falsetto,

Czy byłoby inaczej def str_to_class(str): return vars()[str]?
— Arne

113

Możesz zrobić coś takiego:

globals()[class_name]

6
Podoba mi się to lepiej niż rozwiązanie „eval”, ponieważ (1) jest tak samo łatwe i (2) nie pozostawia cię otwartym na wykonanie dowolnego kodu.
— mjumbewu

2
ale używasz globals()... innej rzeczy, której należy unikać
— Greg

5
@Greg Być może, ale globals()jest prawdopodobnie znacznie mniej zły niż evali wcale nie gorszy niż sys.modules[__name__]AFAIK.
— Laurence Gonsalves

2
Tak, rozumiem, o co ci chodzi, ponieważ tylko chwytasz się tego, nie ustawiając niczego
— Greg

Co jeśli zmienna jest używana w klasie i wiele instancji dla tej klasy zostało utworzonych jednocześnie. Czy spowoduje to problemy, ponieważ jest to zmienna globalna.
— Alok Kumar Singh

98

Chcesz klasy Baz, która mieszka w module foo.bar. Z Python 2.7 chcesz korzystać importlib.import_module(), ponieważ ułatwi to przejście do 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

Z Pythonem <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

Posługiwać się:

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)

To dokładnie obsługuje zarówno klasy w starym, jak i nowym stylu.


Nie potrzebujesz typów.TypeType; to tylko alias dla wbudowanego „typu”.
— Glenn Maynard

1
Tak, wiem, ale uważam, że czytanie jest łatwiejsze. Myślę jednak, że sprowadza się to do preferencji.
— Evan Fosmark

17

Patrzyłem, jak django sobie z tym radzi

django.utils.module_loading ma to

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

Możesz go używać jak import_string("module_path.to.all.the.way.to.your_class")



3

Tak, możesz to zrobić. Zakładając, że twoje klasy istnieją w globalnej przestrzeni nazw, coś takiego to zrobi:

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
W Pythonie 3 nie ma już ClassType.
— riza

1
Użyj isinstance (x, typ).
— Glenn Maynard

3

Jeśli chodzi o wykonanie dowolnego kodu lub niepotrzebne nazwy użytkowników, możesz mieć listę akceptowalnych nazw funkcji / klas, a jeśli dane wejściowe pasują do jednej na liście, są ewaluowane.

PS: Wiem ... trochę późno ... ale to dla każdego, kto natknie się na to w przyszłości.


9
Jeśli zamierzasz zachować listę, równie dobrze możesz zamiast tego prowadzić dyktat od nazwy do klasy. Zatem nie ma potrzeby eval ().
— Fasaxc,

3

Korzystanie z importlib działało dla mnie najlepiej.

import importlib

importlib.import_module('accounting.views') 

Używa to notacji ciąg kropkowej dla modułu python, który chcesz zaimportować.


3

Jeśli naprawdę chcesz odzyskać klasy, które tworzysz za pomocą łańcucha, powinieneś przechowywać je (lub odpowiednio sformułować, odwoływać się ) w słowniku. W końcu pozwoli to także nazwać swoje klasy na wyższym poziomie i uniknąć ujawniania niepożądanych klas.

Przykład: z gry, w której klasy aktorskie są zdefiniowane w Pythonie i chcesz uniknąć innych klas ogólnych, do których dostęp miałby użytkownik.

Inne podejście (jak w poniższym przykładzie) stworzyłoby całą nową klasę, która zawiera dictpowyższe. To by:

  • Umożliwić tworzenie wielu posiadaczy klas w celu łatwiejszej organizacji (np. Jeden dla klas aktorów, a drugi dla rodzajów dźwięku);
  • Ułatwiaj modyfikowanie zarówno posiadacza, jak i prowadzonych klas;
  • I możesz użyć metod klas, aby dodać klasy do dykta. (Chociaż poniższa abstrakcja nie jest tak naprawdę konieczna, służy jedynie ... „ilustracji” ).

Przykład:

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

To zwraca mi:

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

Kolejnym zabawnym eksperymentem związanym z tymi jest dodanie metody, która pikluje ClassHolder, abyś nigdy nie stracił wszystkich zajęć: ^)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.