Auf dem Weg zu Metaklassen
Motivation

class Philosopher1: def the_answer(self, *args): return 42 class Philosopher2: def the_answer(self, *args): return 42 class Philosopher3: def the_answer(self, *args): return 42 plato = Philosopher1() print(plato.the_answer()) kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer())Folgende Ausgabe ist nicht schwer nachzuvollziehen:
42 42Wir stellen fest, dass wir mehrere Kopien der Methode "the_answer" implementiert haben. Das ist ziemlich Fehleranfällig und bedeutet mehr Aufwand bei Wartungen.
Bisher wissen wir, dass der einfachste Weg redundanten Code zu vermeiden darin besteht, eine Basis-Klasse zu schaffen, welche die Methode "the_answer" enthält. Jede Philosopher-Klasse erbt dann von der Basis-Klasse:
class Answers: def the_answer(self, *args): return 42 class Philosopher1(Answers): pass class Philosopher2(Answers): pass class Philosopher3(Answers): pass plato = Philosopher1() print(plato.the_answer()) kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer())Auch hier wieder die gleiche Ausgabe wie im oberen Beispiel:
42 42Auf diese Art hat jede Philosopher-Klasse immer die Methode "the_answer". Nehmen wir an, dass wir noch nicht wissen, ob die Methode gebraucht wird. Nehmen wir weiter an, dass die Entscheidung darüber, ob die Methode gebraucht wird, zur Laufzeit getroffen wird. Diese Entscheidung kann abhängig sein von Konfigurations-Dateien, Benutzereingaben oder Berechnungen.
# the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") if x=="y": required = True else: required = False def the_answer(self, *args): return 42 class Philosopher1: pass if required: Philosopher1.the_answer = the_answer class Philosopher2: pass if required: Philosopher2.the_answer = the_answer class Philosopher3: pass if required: Philosopher3.the_answer = the_answer plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosphers")Das Programm liefert folgende Ausgabe:
Do you need the answer? (y/n): y 42 42Falls wir die Frage nicht mit ,,y'' beantworten, sieht das Ergebnis so aus:
Do you need the answer? (y/n): y 42 42
Auch bei dieser Lösung gibt es noch Nachteile. Sie ist ebenfalls fehleranfällig, weil wir wieder den gleichen Code zu jeder Klasse schreiben müssen. Wenn wir viele Methoden hinzufügen wollen, kann das ziemlich unübersichtlich werden.
Wir können unseren Ansatz verbessern, indem wir eine Manager-Funktion definieren, um redundanten Code weiter zu vermeiden. Die Manager-Funktion übernimmt die Aufgabe, die Klassen entsprechend zu erweitern.
# the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") if x=="y": required = True else: required = False def the_answer(self, *args): return 42 # manager function def augment_answer(cls): if required: cls.the_answer = the_answer class Philosopher1: pass augment_answer(Philosopher1) class Philosopher2: pass augment_answer(Philosopher2) class Philosopher3: pass augment_answer(Philosopher3) plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosphers")Dies ist eine brauchbare Lösung für unser Problem, jedoch müssen wir darauf achten, dass wir den Aufruf der Manager-Funktion "augment_answer" nicht vergessen. Der Code sollte automatisch aufgerufen werden. Wir brauchen eine Möglichkeit um sicherzustellen, dass "bestimmter" Code automatisch im Anschluss einer Klassen-Definition ausgeführt wird.
Im folgenden benutzen wir einen Klassendekorator, d.h. wir schreiben die "augment_answer"-Funktion als Dekoratorfunktion um. Die Philosopher-Klassen können wir nun entsprechend dekorieren.
# the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") if x=="y": required = True else: required = False def the_answer(self, *args): return 42 def augment_answer(cls): if required: cls.the_answer = the_answer # we have to return the class now: return cls @augment_answer class Philosopher1: pass @augment_answer class Philosopher2: pass @augment_answer class Philosopher3: pass plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosphers")Das Programm liefert folgende Ausgabe:
Do you need the answer? (y/n): y 42 42Im kommenden Kapitel lernen wir, dass Metaklassen für diesen Zweck sehr nützlich sein können.