Zum Inhalt

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:

  1. berechnete Eigenschaften nach Außen bereitzustellen
  2. 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