Decorators
Motivation für Decorators
In Python ist alles ein Objekt. Demnach sind auch Funktionen Objekte. Funktionen können in Variablen gespeichert werden und unter anderem auch als Parameter an Funktionen übergeben werden.
Im folgenden wird eine Funktion hello definiert. Die Funktion wird in der Variable greeting gespeichert. Die Variable greeting hat den Datentyp function und kann dadurch auch aufgerufen werden:
def hello(name="world"):
return "hello {}".format(name)
greeting = hello
print(type(greeting)) # <class 'function'>
print(greeting()) # hello world
Im folgenden Beispiel wird eine Funktion make_reverse definiert. Diese Funktion nimmt eine Funktion als Parameter und gibt eine Funktion als Rückgabe zurück. Die reverse Funktion hat dabei die selbe Parameterliste als die Funktion hello:
def hello(name="world"):
return "hello {}".format(name)
def make_reverse(function):
def reverse(name="world"):
greeting = function(name)
return greeting[::-1]
return reverse
reverse_hello = make_reverse(hello)
print(reverse_hello()) # dlrow olleh
print(reverse_hello("students")) # stneduts olleh
Man kann sagen die Funktion make_reverse erzeugt eine Hülle um die Funktion hello und stellt dadurch auch eine Erweiterung dieser dar. Diese Form der Erweiterung könnte umgangssprachlich als "Dekoration" bezeichnet werden. Die Funktion make_reverse stattet die Funktion hello mit zusätzlicher Funktionalität aus, verändert die ursprüngliche Funktion hello im Allgemeinen aber nicht.
Um diese Form der Dekoration einfacher in Python zu integrieren, wurde ein syntaktisches Konzept dazu eingeführt. Dieses Konzept wird als Decorator bezeichnet. Durch die Angabe eines @ und dem entsprechenden Namen des Decorator, kann eine Dekorierung einer beliebigen Funktion durchgeführt werden:
def make_reverse(function):
def reverse(name="world"):
greeting = function(name)
return greeting[::-1]
return reverse
@make_reverse
def hello(name="world"):
return "hello {}".format(name)
print(hello()) # dlrow olleh
print(hello("students")) # stneduts olleh
Mit @make_reverse wird der Decorator an die Funktion hello gesetzt. Ein Decorator muss eine Funktion als Parameter aufnehmen und eine Funktion als Rückgabewert zurückführen.
Verschachtelung von Decorators
Decorators können auch verschachtelt werden, sodass eine Funktion von mehreren Decorators erweitert werden kann. Die Ausführungsreihenfolge ist dabei immer von Außen nach Innen. Im unten angeführten Beispiel ist die Ausführungsreihenfolge folgendermaßen: (1) reverse, (2) spacing und (3) hello.
def spacing(function):
def inner(name="world"):
return " ".join([char for char in function(name)])
return inner
def reverse(function):
def inner(name="world"):
greeting = function(name)
return greeting[::-1]
return inner
@reverse
@spacing
def hello(name="world"):
return "hello {}".format(name)
print(hello()) # d l r o w o l l e h
Built-in Decorators
Die Programmiersprache Python definiert bereits eine Vielzahl hilfreicher Decorators. Im folgenden sollen die Decorators @property, @classmethod und setter näher beschrieben werden.
@property Decorator
Durch den Decorator @property kann eine Methode hinter der Abfrage einer Eigenschaft verborgen werden. Dies kann aus zweierlei Gründen nützlich sein:
- berechnete Eigenschaften nach Außen bereitzustellen
- private Eigenschaften nach Außen bereitzustellen
Im folgenden werden die Eigenschaften celsius und fahrenheit der Klasse Temperature über den entsprechenden @property Decorator bereitgestellt:
class Temperature:
def __init__(self, celsius):
self.__celsius = celsius
@property
def celsius(self):
return self.__celsius # private Eigenschaft
@property
def fahrenheit(self):
return (self.__celsius * 1.8) + 32 # berechnete Eigenschaft
livingroom = Temperature(26)
print(livingroom.celsius) # 26
print(livingroom.fahrenheit) # 78.8 ...
setter Decorator
Eigenschaften, welche mittels @property Decorator, definiert sind, können auch über einen Setter verfügen. Ein Setter ist eine Methode, welche eine Eigenschaft des Objektes setzt. Anders als ein direktes Setzen der Eigenschaft kann mit dem Setter auch eine Gültigkeitsprüfung des zu setzenden Wertes vorgenommen werden.
Im folgenden Beispiel wird für die Eigenschaft celsius ein Setter festgelegt. Der Setter wird über den Decorator @Eigenschaftsname.setter gesetzt, im Beispiel also @celsius.setter:
class Temperature:
def __init__(self, celsius):
self.__celsius = celsius
@property
def celsius(self):
return self.__celsius
@property
def fahrenheit(self):
return (self.__celsius * 1.8) + 32
@celsius.setter
def celsius(self, celsius):
assert type(celsius) in (int, float)
self.__celsius = celsius
livingroom = Temperature(26)
livingroom.celsius = 16
print(livingroom.celsius) # 16
print(livingroom.fahrenheit) # 68.8
@classmethod Decorator
Mit dem Decorator @classmethod können Klassen-Methoden definiert werden, welche als Parameter eine Referenz auf die Klasse (cls) bekommen. Der Parameter cls ist dabei nicht mit self zu verwechseln. Der Aufruf der Klassen-Methode ist entweder über die Instanz oder die Klasse durchführbar:
class Vehicle:
__production_count = 0
def __init__(self, name):
self.name = name
Vehicle.__production_count += 1
@classmethod
def get_production_count(cls):
return cls.__production_count
v1 = Vehicle("Audi")
v2 = Vehicle("BMW")
v3 = Vehicle("Tesla")
print(Vehicle.get_production_count()) # 3
print(v1.get_production_count()) # 3
print(v2.get_production_count()) # 3
print(v3.get_production_count()) # 3