Dekoratoren
Python unterstützt seit Version 2.2 neben den Instanzmethoden auch Klassenmethoden und statische Methoden. Durch die Klassenmethode wird die Methode an die Klasse selbst, durch eine statische Methode an den Namensraum der Klasse gebunden.
Dekoratoren sind ein mächtiges Werkzeug in Python um aspektorientiert zu programmieren.
Ein Dekorator ist ein aufrufbares Python Objekt, welches die zu dekorierende Funktion als ein Argument annimmt. Wir hatten ja gesehen, dass so gut wie alles in Python ein Objekt ist. Genau so ist es mit Funktionen. Wir können nun lokale Funktionen innerhalb von Funktionen anlegen und diese entsprechend aufrufen.
Ein Dekorator muss ein Python Objekt sein, das mit einem Argument aufgerufen werden kann.
Sowohl Funktionen mit inneren Funktionen (Closures) als auch Objekte (Funktoren), die aufrufbar sind, eignen sich, um Dekoratoren zu implementieren.
Funktion – Closures
def decorator (func):
def closure (*args,**kwargs):
print("Hallo", func)
try:
return func (*args,**kwargs)
finally:
print("Tschüss", func)
return closure
Diese Funktion gibt closure zurück, welches Hello aufruft und func aufruft, also die Funktion die unserem decorator übergeben wurde.
Mehr zu Closures:
def gruesse(name='Denis'):
print('Die gruess funktion wurde ausgeführt')
def hallo():
return 'Das ist innerhalb der hallo Funktion'
def willkommen():
return "Das ist innerhalb der willkommen Funktion"
print(hallo())
print(willkommen())
print("Und weiter in der gruesse Funktion")
gruesse()
Die gruess funktion wurde ausgeführt
Das ist innerhalb der hallo Funktion
Das ist innerhalb der willkommen Funktion
Und weiter in der gruesse Funktion
Wenn wir nun jedoch versuchen die hallo Funktion aufzurufen:
hallo()
ERROR
Das bedeutet, dass Funktionen innerhalb von Funktionen auch nur von den Überfunktionen gesehen werden. Sie sind ja eine Closure.
Weiteres Beispiel für Dekoratoren in Python
def funktion1():
return 'Ich bin funktion1'
def funktion2(func):
print(func())
print('Mein funktion2 Code kommt hier')
funktion2(funktion1)
Ich bin funktion1
Mein funktion2 Code kommt hier
Du kannst Funktionen jedoch auch innerhalb von Variablen speichern und somit als Parameter verwenden.
def funktion1():
return 'Ich bin funktion1'
x = funktion1
def funktion2(func):
print(func())
print('Mein funktion2 Code kommt hier')
funktion2(x)
Beim zuweisen der funktion1 Funktion zu x, verwende ich keine () da ich die Funktion hier ja nicht aufrufe, sondern weise sie zu.
Schauen wir uns mal an, wie wir einen Dekorator grundsätzlich nutzen können.
def decorator_funktion(func):
def closure_funktion():
print('Dies wird vor unserer übergebenen Funktion aufgerufen')
func()
print('Dies nach unserer übergebenen Funktion')
return closure_funktion
def func_braucht_dekorator():
print('Diese Funktion benötigt einen Dekorator')
Wenn wir die Funktion func_braucht_dekorator() nun aufrufen, passiert nichts besonderes:
func_braucht_dekorator()
Diese Funktion benötigt einen Dekorator
Wenn wir dieser Funktionen nun einen Dekorator übergeben, dann sieht das so aus:
func_braucht_dekorator = decorator_funktion(func_braucht_dekorator)
Dies wird vor unserer übergebenen Funktion aufgerufen
Diese Funktion benötigt einen Dekorator
Dies nach unserer übergebenen Funktion
Vereinfach können wir das jedoch nun, indem wir das @ Zeichen verwenden:
Denn den Code…
func_braucht_dekorator = decorator_funktion(func_braucht_dekorator)
… können wir uns sparen, wenn wir bei der Erstellung unserer Funktion vorne anfügen, welchen Dekorator wird ihr mitgeben wollen:
@decorator_funktion
def func_braucht_dekorator():
print('Diese Funktion benötigt einen Dekorator')
Das @ Zeichen, ist in Python das Dekorator Symbol.
def decorator_funktion(func):
def closure_funktion():
print('Dies wird vor unserer übergebenen Funktion aufgerufen')
func()
print('Dies nach unserer übergebenen Funktion')
return closure_funktion
@decorator_funktion
def func_braucht_dekorator():
print('Diese Funktion benötigt einen Dekorator')
func_braucht_dekorator()
Gibt aus:
Dies wird vor unserer übergebenen Funktion aufgerufen
Diese Funktion benötigt einen Dekorator
Dies nach unserer übergebenen Funktion
Vor- und Nachteile von Dekoratoren
Die Vorteile bestehen darin, dass mehrere Dekorierer hintereinandergeschaltet werden können; die Dekorierer können zur Laufzeit und sogar nach der Instanziierung ausgetauscht werden. Die zu dekorierende Klasse ist nicht unbedingt festgelegt (wohl aber deren Schnittstelle). Zudem können lange und unübersichtliche Vererbungshierarchien vermieden werden.
Das Muster hat eine Gefahr: Da eine dekorierte Komponente nicht identisch mit der Komponente selbst ist (als Objekt), muss man beim Testen auf Objekt-Identität vorsichtig sein. (Ein Vergleich kann falsch ausgehen, obwohl dieselbe Komponente gemeint ist.) Zudem müssen bei der Verwendung von dekorierten Komponenten die Nachrichten vom Dekorierer an das dekorierte Objekt weitergeleitet werden.
Dekorierer können uns natürlich einige Vorteile liefern, jedoch dürfen wir nicht vergessen, dass der Code dadurch insgesammt (ca 3x) langsamer ist, als eine Funktion außerhalb anzulegen und aufzurufen.