2008-10-21 5 views
6

Ich arbeite an einer Webanwendung, die je nach Benutzereingaben einen variablen Satz von Modulen zurückgibt. Jedes Modul ist eine Python-Klasse mit einem Konstruktor, der einen einzelnen Parameter akzeptiert und eine Eigenschaft '.html' besitzt, die die Ausgabe enthält.Der beste Ansatz mit dynamischen Klassen mit Python globals()

die Klasse Ziehen dynamisch aus dem globalen Namensraum funktioniert:

result = globals()[classname](param).html 

Und es ist sicherlich prägnanter als:

if classname == 'Foo': 
    result = Foo(param).html 
elif classname == 'Bar': 
    ... 

Was ist der beste Weg, dies zu schreiben gilt, stilistisch? Gibt es Risiken oder Gründe, den globalen Namespace nicht zu verwenden?

Antwort

6

Ein Fehler mit diesem Ansatz besteht darin, dass es dem Benutzer die Möglichkeit geben kann, mehr als Sie wollen. Sie können die beliebige Einzelparameterfunktion in diesem Namespace aufrufen, indem Sie nur den Namen angeben. Sie können helfen, dies mit ein paar Überprüfungen zu verhindern (zB isinstance (SomeBaseClass, theClass), aber es ist wahrscheinlich besser, diesen Ansatz zu vermeiden. Ein weiterer Nachteil ist, dass es Ihre Klassenplatzierung einschränkt. Wenn Sie mit Dutzenden solcher Klassen enden und entscheiden mehrere alternative Optionen zu gruppieren sie in Module, stoppt Ihre Lookup-Code arbeiten

Sie haben.

  1. eine explizite Abbildung erstellen:

    class_lookup = {'Class1' : Class1, ... } 
    ... 
    result = class_lookup[className](param).html 
    

    obwohl dies den Nachteil, dass Sie um al wieder aufzulisten l die Klassen.

  2. Verschachteln Sie die Klassen in einem umschließenden Bereich. Z.B. definieren sie in ihrem eigenen Modul oder innerhalb einer äußeren Klasse:

    class Namespace(object): 
        class Class1(object): 
         ... 
        class Class2(object): 
         ... 
    ... 
    result = getattr(Namespace, className)(param).html 
    

    Sie belichten inadvertantly ein paar zusätzliche Klassenvariablen hier aber (__bases__, __getattribute__ etc.) - wahrscheinlich nicht verwertbar, aber nicht perfekt.

  3. Konstruieren Sie ein Nachschlage-Dict aus dem Unterklassenbaum. Machen Sie alle Ihre Klassen von einer einzigen Basisklasse abhängig. Wenn alle Klassen erstellt wurden, untersuchen Sie alle Basisklassen und füllen Sie ein Dict daraus. Dies hat den Vorteil, dass Sie Ihre Klassen überall definieren können (zB in separaten Modulen), und solange Sie die Registrierung erstellen, nachdem alle erstellt wurden, werden Sie sie finden.

    def register_subclasses(base): 
        d={} 
        for cls in base.__subclasses__(): 
         d[cls.__name__] = cls 
         d.update(register_subclasses(cls)) 
        return d 
    
    class_lookup = register_subclasses(MyBaseClass) 
    

    Eine erweiterte Variante des oben ist selbstregistrierende Klassen zu verwenden - eine Metaklasse erstellen als automatisch alle erstellten Klassen in einem dict registriert. Dies ist wahrscheinlich zu viel für diesen Fall - es ist jedoch in einigen "Benutzer-Plugins" -Szenarien nützlich.

4

Zunächst scheint es, als ob Sie das Rad ein wenig neu erfinden ... die meisten Python-Web-Frameworks (CherryPy/TurboGears ist, was ich weiß) enthalten bereits eine Möglichkeit zum Senden von Anfragen an bestimmte Klassen basierend auf dem Inhalt der URL oder der Benutzereingabe.

Es gibt nichts falsch mit der Art, wie Sie es tun, wirklich, aber in meiner Erfahrung neigt dazu, eine Art "fehlende Abstraktion" in Ihrem Programm anzuzeigen. Sie verlassen sich im Grunde genommen auf den Python-Interpreter, um eine Liste der Objekte zu speichern, die Sie möglicherweise benötigen, anstatt sie selbst zu speichern.

So, als einen ersten Schritt, möchten Sie vielleicht nur ein Wörterbuch aller Klassen, die Sie vielleicht nennen wollen:

dispatch = {'Foo': Foo, 'Bar': Bar, 'Bizbaz': Bizbaz} 

Zunächst wird dies nicht viel Unterschied machen. Aber wenn Ihre Webanwendung wächst, können Sie mehrere Vorteile finden: (a) Sie werden nicht in Namensraumkonflikte geraten, (b) unter Verwendung von globals() können Sie Sicherheitsprobleme haben, wo ein Angreifer im Wesentlichen auf jedes globale Symbol in Ihrem Programm zugreifen kann wenn sie einen Weg finden können, um ein beliebiges classname in Ihr Programm zu injizieren, (c) wenn Sie jemals wollen, dass classname etwas anderes als der tatsächliche exakte Klassenname ist, ist die Verwendung Ihres eigenen Wörterbuchs flexibler, (d) Sie können das ersetzen dispatch Wörterbuch mit einer flexibleren benutzerdefinierten Klasse, die Datenbankzugriff oder etwas ähnliches tut, wenn Sie die Notwendigkeit finden.

Die Sicherheitsprobleme sind besonders hervorstechend für eine Webanwendung. Doing globals()[variable] wo variable eingegeben wird von einem Webformular ist nur um Ärger zu bitten.

0

Eine weitere Möglichkeit, die Karte zwischen Klassennamen und Klassen zu bauen:

Wenn Klassen definiert, ein Attribut zu einer Klasse hinzufügen, die Sie in der Lookup-Tabelle setzen wollen, zum Beispiel:

class Foo: 
    lookup = True 
    def __init__(self, params): 
     # and so on 

Sobald dies erledigt ist, wird die Lookup Map erstellt:

class_lookup = zip([(c, globals()[c]) for c in dir() if hasattr(globals()[c], "lookup")])