Dynamische Klassendefinition in Python

Die dynamische Objektdefinition kann als Definition zur Laufzeit verstanden werden. Im Gegensatz zu einer statischen Definition, die in der bekannten Schlüsselwortdefinition einer Klasse verwendet wird class, verwendet eine dynamische Definition eine Inline-Klasse type.



Geben Sie metaclass ein



Die Typklasse wird häufig verwendet, um den Typ eines Objekts abzurufen. Zum Beispiel so:



h = "hello"
type(h)
<class 'str'>


Aber es hat andere Verwendungszwecke. Es kann neue Typen initialisieren.Wie Sie wissen, ist alles in Python ein Objekt. Daraus folgt, dass alle Definitionen Typen haben, einschließlich Klassen und Objekte. Zum Beispiel:



class A:
    pass
type(A)
<class 'type'>


Es ist möglicherweise nicht ganz klar, warum einer Klasse typeim Gegensatz zu ihren Instanzen ein Klassentyp zugewiesen wird :



a = A()
type(a)
<class '__main__.A'>


Dem Objekt awird eine Klasse als Typ zugewiesen. Auf diese Weise behandelt der Interpreter das Objekt als Instanz der Klasse. Die Klasse selbst hat einen Klassentyp, typeda sie ihn von der Basisklasse erbt object:



A.__bases__
(<class 'object'>,)


Klassentyp object:



type(object)
<class 'type'>


Die Klasse wird objectstandardmäßig von allen Klassen geerbt, dh:



class A(object):
    pass


Gleich wie:



class A:
    pass


Die zu definierende Klasse erbt die Basisklasse als Typ. Dies erklärt jedoch nicht, warum die Basisklasse vom Klassentyp objectist type. Der Punkt ist, es typeist eine Metaklasse. Wie Sie bereits wissen, erben alle Klassen von der Basisklasse object, die vom Typ Metaklasse ist type. Daher haben auch alle Klassen diesen Typ, einschließlich der Metaklasse selbst type:



type(type)
<class 'type'>


Dies ist der "Endpunkt der Eingabe" in Python. Die Typvererbungskette ist für die Klasse geschlossen type. Die Metaklasse typedient als Basis für alle Klassen in Python. Dies lässt sich leicht überprüfen:



builtins = [list, dict, tuple]
for obj in builtins:
    type(obj)
<class 'type'>
<class 'type'>
<class 'type'>


Eine Klasse ist ein abstrakter Datentyp, und Instanzen davon haben eine Klassenreferenz als Typ.



Initialisieren neuer Typen mit der Typklasse



Bei der Überprüfung von Typen wird die Klasse typemit einem einzigen Argument initialisiert:



type(object) -> type


Dabei wird der Typ des Objekts zurückgegeben. Die Klasse implementiert jedoch eine andere Initialisierungsmethode mit drei Argumenten, die einen neuen Typ zurückgibt:



type(name, bases, dict) -> new type


Geben Sie die Initialisierungsparameter ein



  • name

    Eine Zeichenfolge, die den Namen der neuen Klasse (Typ) definiert.
  • bases

    Ein Tupel von Basisklassen (Klassen, die die neue Klasse erben wird).
  • dict

    Wörterbuch mit den Attributen der zukünftigen Klasse. Normalerweise mit Zeichenfolgen in Schlüsseln und aufrufbaren Typen in Werten.


Dynamische Klassendefinition



Wir initialisieren die Klasse des neuen Typs, indem wir alle erforderlichen Argumente bereitstellen und aufrufen:



MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>


Sie können wie gewohnt mit der neuen Klasse arbeiten:



m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>


Darüber hinaus entspricht die Methode der üblichen Klassendefinition:



class MyClass:
    pass


Dynamische Definition von Klassenattributen



Eine leere Klasse hat wenig Sinn, daher stellt sich die Frage: Wie werden Attribute und Methoden hinzugefügt?



Betrachten Sie zur Beantwortung dieser Frage den anfänglichen Initialisierungscode:



MyClass = type(“MyClass”, (object, ), dict())


In der Regel werden der Klasse in der Initialisierungsphase Attribute als drittes Argument hinzugefügt - das Wörterbuch. Sie können Attributnamen und Werte im Wörterbuch angeben. Zum Beispiel könnte es eine Variable sein:



MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'


Dynamische Methodendefinition



Aufrufbare Objekte können auch an das Wörterbuch übergeben werden, z. B. Methoden:



def foo(self):
    return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'


Diese Methode hat einen wesentlichen Nachteil - die Notwendigkeit, die Methode statisch zu definieren (ich denke, dass dies im Zusammenhang mit Metaprogrammierungsaufgaben als Nachteil angesehen werden kann). Außerdem selfsieht die Definition einer Methode mit einem Parameter außerhalb des Klassenkörpers seltsam aus. Kehren wir also zur dynamischen Initialisierung einer Klasse ohne Attribute zurück:



MyClass = type(“MyClass”, (object, ), dict())


Nach dem Initialisieren einer leeren Klasse können Sie ihr dynamisch Methoden hinzufügen, dh ohne explizite statische Definition:



code = compile('def foo(self): print(“bar”)', "<string>", "exec")


compileIst eine integrierte Funktion, die Quellcode in ein Objekt kompiliert. Der Code kann von Funktionen exec()oder ausgeführt werden eval().



Funktionsparameter kompilieren



  • Quelle

    Quellcode kann ein Link zu einem Modul sein.
  • Dateiname

    Der Name der Datei, in die das Objekt kompiliert wird.
  • Modus

    Wenn angegeben "exec", kompiliert die Funktion den Quellcode in ein Modul.


Das Ergebnis der Arbeit compileist ein Klassenobjekt code:



type(code)
<class 'code'>


Das Objekt codemuss in eine Methode konvertiert werden. Da eine Methode eine Funktion ist, konvertieren wir zunächst ein Klassenobjekt codein ein Klassenobjekt function. Importieren Sie dazu das Modul types:



from types import FunctionType, MethodType


Ich werde es importieren MethodType, da ich es später benötigen werde, um die Funktion in eine Klassenmethode zu konvertieren.



function = FunctionType(code.co_consts[0], globals(), “foo”)


Parameter der FunctionType-Initialisierungsmethode



  • code

    Klassenobjekt code. code.co_consts[0]Ist ein Aufruf eines co_constsKlassendeskriptors code, bei dem es sich um ein Tupel mit Konstanten im Code des Objekts handelt. Stellen Sie sich ein Objekt codeals Modul mit einer einzelnen Funktion vor, die wir als Klassenmethode hinzufügen möchten. 0Ist sein Index, da es die einzige Konstante im Modul ist.
  • globals()

    Wörterbuch der globalen Variablen.
  • name

    Optionaler Parameter, der den Namen der Funktion angibt.


Das Ergebnis ist eine Funktion:



function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>


Als Nächstes müssen Sie diese Funktion als Klassenmethode hinzufügen MyClass:



MyClass.foo = MethodType(function, MyClass)


Ein ziemlich einfacher Ausdruck, der unsere Funktion einer Klassenmethode zuweist MyClass.



m = MyClass()
m.foo()
bar


Warnung



In 99% der Fälle kommen Sie mit statischen Klassendefinitionen aus. Das Konzept der Metaprogrammierung ist jedoch gut darin, die Interna von Python aufzudecken. Höchstwahrscheinlich wird es für Sie schwierig sein, die Anwendung der hier beschriebenen Methoden zu finden, obwohl es in meiner Praxis einen solchen Fall gab.



Haben Sie mit dynamischen Objekten gearbeitet? Vielleicht in anderen Sprachen?



Links






All Articles