2009-07-24 24 views
125

Wenn ich eine Zeichenfolge als Benutzereingabe für eine Python-Funktion gebe, würde ich gerne ein Klassenobjekt daraus machen, wenn es eine Klasse mit diesem Namen im aktuell definierten Namespace gibt. Im Wesentlichen möchte ich die Implementierung für eine Funktion, die diese Art von Ergebnis erzeugen wird:Zeichenfolge in Python-Klassenobjekt konvertieren?

Ist das möglich?

+2

Es gibt einige nützliche Antworten hier, aber ich fand die Antwort auf diese Frage besonders nützlich: http://stackoverflow.com/q usesions/452969/does-python-habe-ein-äquivalent-zu-java-class-forname – Tom

Antwort

79

Dies scheint am einfachsten.

+24

Beware die Verzweigung der Verwendung von Eval. Sie sollten besser sicherstellen, dass die übergebene Zeichenfolge nicht vom Benutzer stammt. – Overclocked

+16

Die Verwendung von eval() lässt die Tür für die Ausführung von beliebigem Code offen, aus Sicherheitsgründen sollte sie jedoch vermieden werden. –

+41

Eval lässt die Tür für nichts offen. Wenn Sie böswillige bösartige Benutzer haben, die böswillig und bösartig falsche Werte an eval übergeben, können sie nur die Python-Quelle bearbeiten. Da sie nur die Python-Quelle bearbeiten können, ist die Tür, war und wird immer offen sein. –

87

Man könnte so etwas wie tun:

globals()[class_name] 
+5

Ich mag das besser als die 'eval' Lösung, weil (1) es genauso einfach ist und (2) es nicht verlässt Sie öffnen für die Ausführung willkürlichen Codes. – mjumbewu

+0

aber Sie verwenden 'globals()' ... eine andere Sache, die vermieden werden sollte – Greg

+3

@Greg Vielleicht, aber 'globals()' ist wohl weit weniger schlecht als 'eval', und nicht wirklich schlimmer als' sys. Module [__ Name __] 'AFAIK. –

106

Diese funktionieren könnte:

import sys 

def str_to_class(str): 
    return getattr(sys.modules[__name__], str) 
+0

Was passiert, wenn die Klasse nicht existiert? – riza

+1

Es funktioniert nur für die im aktuellen Modul definierte Klasse – luc

+33

Entschuldigen Sie, diese Antwort von den Toten zu erheben. @luc ist richtig, aber wir können die letzte Zeile durch 'return reduce (getattr, str.split (". "), sys.modules [__ name __])' ersetzen. Dies hat den gleichen Effekt wie 'eval()', erlaubt aber nicht das Ausführen von beliebigem Code. – jollyroger

2

Ja, Sie können dies tun. Angenommen, Ihre Klassen im globalen Namespace vorhanden ist, wird so etwas tun es:

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

In Python 3 gibt es keinen ClassType mehr. – riza

+0

Verwenden Sie isinstance (x, type). –

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

Diese Griffe genau sowohl im alten Stil und neuen Stil Klassen.

+0

Sie benötigen keine types.TypeType; Es ist nur ein Alias ​​für den eingebauten "Typ". –

+1

Ja, ich weiß, aber ich fühle es ist klarer zu lesen. Ich denke, es läuft nur auf Präferenz, obwohl. –

71

Sie wollen die Klasse "Baz", die in Modul "foo.bar" lebt. Mit Python 2.7, Sie verwenden möchten importlib.import_module(), da dies zu Python machen den Übergang 3 einfacher:

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 

mit 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 

Verwendung:

loaded_class = class_for_name('foo.bar', 'Baz') 
+1

nice one, es funktioniert auch, wenn die Klasse nicht ursprünglich in diesem Modul importiert wurde – parxier

2

In Bezug auf die Ausführung willkürlichen Codes oder unerwünschte Benutzer übergebenen Namen, könnten Sie eine Liste von akzeptablen Funktion/Klassennamen und wenn Die Eingabe stimmt mit einer in der Liste überein, sie wird ausgewertet.

PS: Ich weiß .... irgendwie spät .... aber es ist für jeden anderen, der in Zukunft darüber stolpert.

+6

Wenn Sie die Liste verwalten möchten, könnte auch ein Dict von Name zu Klasse beibehalten. Dann gibt es keine Notwendigkeit für eval(). – Fasaxc

1

Mit importlib funktionierte das beste für mich.

import importlib 

importlib.import_module('accounting.views') 

Dies verwendet String Punktnotation für das Python-Modul, das Sie importieren möchten.

6

Ich habe untersucht, wie django diese Griffe

django.utils.module_loading hat dieses

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

Sie es

1

wie import_string("module_path.to.all.the.way.to.your_class") verwenden können, wenn Sie wirklich Klassen, die Sie machen abrufen möchten Mit einer Zeichenkette sollten Sie sie in einem Wörterbuch speichern (oder richtig formuliert, Referenz). Schließlich können Sie Ihre Klassen auch auf einer höheren Ebene benennen und vermeiden, dass unerwünschte Klassen angezeigt werden.

Beispiel aus einem Spiel, in dem Schauspielerklassen in Python definiert sind und Sie vermeiden möchten, dass andere allgemeine Klassen durch Benutzereingaben erreicht werden.

Ein anderer Ansatz (wie im Beispiel unten) würde eine ganz neue Klasse bilden, die die obigen dict enthält. Dies würde:

  • Erlauben mehrere Klasseninhaber für eine einfachere Organisation gemacht werden (wie eine für Schauspieler Klassen und andere für Arten von Sound);
  • Änderungen sowohl am Halter als auch an den gehaltenen Klassen vornehmen;
  • Und Sie können Klassenmethoden verwenden, um Klassen zum Diktat hinzuzufügen. (Obwohl die Abstraktion unten nicht wirklich notwendig ist, ist es nur für ... "illustration").

Beispiel:

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

Das gibt mir:

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

Ein weiteres lustiges Experiment mit denen zu tun, ist eine Methode hinzuzufügen, die die ClassHolder so Pickles Sie nie Sie alle Klassen verlieren tat: ^)