Python De­co­ra­tors sind ein Weg, Einfluss auf die grund­le­gen­de Funk­tio­na­li­tät einer Funktion zu erweitern, ohne den ihr zu­grun­de­lie­gen­den Quellcode zu verändern.

Was sind Funk­ti­ons­de­ko­ra­to­ren und wofür werden sie genutzt?

Die Nutzung von Python De­co­ra­tors wird in vielen Python-Tutorials nicht näher erläutert. Der Grund hierfür: Um Funk­ti­ons­de­ko­ra­to­ren zu verstehen, ist es nötig, sich zunächst mit Funk­tio­nen selbst gut aus­zu­ken­nen. Python De­co­ra­tors erkennen Sie im Code durch einen eigenen Python-Operator: Das „@“-Zeichen gefolgt vom Namen des Funk­ti­ons­de­ko­ra­tors.

Die grund­le­gen­de Syntax eines Python-Decorator-Aufrufs können Sie im folgenden Code­bei­spiel sehen, das al­ler­dings keine Funk­tio­na­li­tät im­ple­men­tiert:

@dekorator
def funktion():
	pass

Der in „dekorator“ hin­ter­leg­te Code würde in diesem Beispiel aus­ge­führt, wenn die Funktion namens „funktion“ auf­ge­ru­fen wird.

Oft werden Funk­ti­ons­de­ko­ra­to­ren auch in der ob­jekt­ori­en­tier­ten Pro­gram­mie­rung mit Python genutzt. So gibt es z. B. den Decorator Python Property. Dieser wird als Äqui­va­lent zu Getter- und Setter-Methoden in anderen Pro­gram­mier­spra­chen verwendet.

Tipp

Python ist nicht nur dank prak­ti­schen Kon­struk­ten wie Funk­ti­ons­de­ko­ra­to­ren eine ideale Pro­gram­mier­spra­che für Web­pro­jek­te. Ebenfalls her­vor­ra­gend geeignet für Web­pro­jek­te ist IONOS Deploy Now. Sie können Ihr Projekt mit Deploy Now einfach via GitHub be­reit­stel­len und builden und behalten so jederzeit den Überblick.

Python De­co­ra­tors im Einsatz

Kern­funk­tio­na­li­tät mithilfe von Python De­co­ra­tors erweitern

Python De­co­ra­tors werden in den meisten Fällen genutzt, um die Kern­funk­tio­na­li­tät einer Funktion zu erweitern. Dies ist praktisch, wenn Sie ein und dieselbe Grund­funk­ti­on in mehreren Usecases einsetzen und in­di­vi­du­ell erweitern möchten. Ein einfaches, aber bereits lauf­fä­hi­ges Code­bei­spiel ver­deut­licht den Einsatz von Python De­co­ra­tors zur Er­wei­te­rung von Funk­tio­na­li­tät:

def dec(funktion):
	def foo(x):
		print("Vor dem Funktionsaufruf von " + funktion.__name__)
		funktion(x)
print("Nach dem Funktionsaufruf von " + funktion.__name__)
	return foo
@dec
def bar(y):
	print("Funktionsaufruf von bar mit dem Wert " + str(y))
bar("Test")

Zunächst wird im Code­bei­spiel der Decorator namens „dec“ erstellt, der wiederrum eine Funktion namens „foo“ be­inhal­tet. Wie Sie sehen, handelt es sich bei dem Funk­ti­ons­de­ko­ra­tor im Grunde genommen um nichts anderes als eine ei­gen­stän­di­ge Wrapper-Funktion, die in unserem Fall eine andere Funktion namens „foo“ enthält. In „foo“ wird zunächst aus­ge­ge­ben, dass wir uns vor dem Funk­ti­ons­auf­ruf der dem Dekorator im Parameter „funktion“ über­ge­be­nen Funktion befinden. An­schlie­ßend wird die Funktion aus dem Parameter aus­ge­führt. Danach erfolgt erneut ein Python-print-Aufruf, der mitteilt, dass wir uns nach dem Funk­ti­ons­auf­ruf der als Parameter über­ge­be­nen Funktion befinden.

Der zweite Teil des Codes besteht aus einer Funk­ti­ons­de­fi­ni­ti­on der Funktion namens „bar“, die einen Über­ga­be­pa­ra­me­ter namens „y“ ent­ge­gen­nimmt. Die Funk­tio­na­li­tät von bar ist leicht nach­zu­voll­zie­hen: Sie gibt den Satz „Funk­ti­ons­auf­ruf von bar mit dem Wert y“ auf dem Bild­schirm aus, wobei für y der als Parameter über­ge­be­ne Wert ein­ge­setzt wird. Das Besondere an der bar-Funktion ist, dass sie dekoriert wurde. Dies erkennen Sie im Code­bei­spiel an der Zeile „@dec“ vor der Funk­ti­ons­de­fi­ni­ti­on.

Doch was genau heißt es, dass die Funktion dekoriert wurde? An­ge­nom­men, wir hätten den Python Decorator, also die Codezeile „@dec“, weg­ge­las­sen. Der Funk­ti­ons­auf­ruf von „bar“, der unser Code­bei­spiel ab­schließt, würde dann folgenden Output liefern:

Funktionsaufruf von bar mit dem Wert Test

Hier passiert genau das, was man vom Funk­ti­ons­auf­ruf erwartet: Der für den Parameter y über­ge­be­ne Python String „Test“ wird in das print-Statement ein­ge­setzt. Ent­spre­chend sieht die Ausgabe der Funktion aus.

Be­trach­ten wir nun den Output desselben „bar“-Aufrufs, wobei die „bar“-Funktion dieses Mal mit unserem Python Decorator dekoriert wurde:

Vor dem Funktionsaufruf von bar
Funktionsaufruf von bar mit dem Wert Test
Nach dem Funktionsaufruf von bar

Was Sie sehen, über­rascht Sie viel­leicht: Nachdem unsere Funktion dekoriert wurde, gibt sie nicht mehr nur die Ausgabe des eigenen print-State­ments auf dem Bild­schirm aus. Vielmehr wurde die Ausgabe unseres Funk­ti­ons­de­ko­ra­tors ein­ge­kap­selt, sodass nun auch die beiden print-State­ments aus der Hilfs­funk­ti­on „foo“ be­rück­sich­tigt werden. Die Kern­funk­tio­na­li­tät von „bar“ wurde also erweitert, indem durch den Einsatz des Python De­co­ra­tors zwei weitere print-Ausgaben hin­zu­ge­nom­men wurden.

Natürlich ist dieses Beispiel ar­ti­fi­zi­ell und verfolgt keine tiefere Pro­gram­mier­lo­gik. Um sich einen Überblick über die Funk­ti­ons­wei­se von Python De­co­ra­tors zu ver­schaf­fen, reicht es al­ler­dings aus. In der Dekorator-Funktion können Sie selbst­ver­ständ­lich beliebige Python-Funk­tio­na­li­tät einbinden.

Wie­der­keh­ren­de Be­din­gun­gen mit Python De­co­ra­tors abfragen

Es kann sein, dass Sie die Aus­füh­rung be­stimm­ter Funk­tio­nen an eine Bedingung knüpfen möchten. Hierfür sind Ihnen ver­mut­lich bereits die Python-if-else-State­ments bekannt. Wenn es sich bei diesen Be­din­gun­gen aber um solche handelt, die an ver­schie­de­ner Stelle überprüft werden müssen, kann es für die Über­sicht­lich­keit Ihres Codes hilfreich sein, die Bedingung in einen Python Decorator aus­zu­la­gern.

Auch hier hilft ein Code­bei­spiel, um sich die Anwendung des De­co­ra­tors zu ver­an­schau­li­chen. Manche ma­the­ma­ti­schen Ope­ra­to­ren sind nur auf den na­tür­li­chen Zahlen definiert. Hilfreich wäre es daher, einen Funk­ti­ons­de­ko­ra­tor zu haben, der überprüft, ob der Über­ga­be­pa­ra­me­ter einer Funktion eine na­tür­li­che Zahl ist.

def natuerliche_zahl(funktion):
	def test(x):
		if type(x) == int and x > 0:
			return funktion(x)
		else:
			raise Exception("Das Argument ist keine natürliche Zahl")
@natuerliche_zahl
def fak(n):
	if n == 1:
		return 1
	else:
		return n * fak(n-1)
print(fak(5))
print(fak(-1))

Im obigen Code de­fi­nie­ren wir zunächst unseren Python Decorator namens „na­tuer­li­che_zahl“, der testet, ob es sich bei dem Argument der ihm über­ge­be­nen Funktion namens „funktion“ um eine na­tür­li­che Zahl handelt. Hierzu wird in der if-Bedingung zunächst der Typ des Arguments überprüft. Außerdem wird getestet, ob es sich bei dem Argument um eine positive Zahl größer als 0 handelt. Wenn dies der Fall ist, wird die dem Dekorator als Parameter über­ge­be­ne Funktion aus­ge­führt. An­de­ren­falls wird eine Exception geworfen, die mitteilt, dass es sich bei dem Argument der Funktion um keine na­tür­li­che Zahl handelt.

Die Funk­ti­ons­wei­se unseres Python De­co­ra­tors kann man sich in der Praxis ver­deut­li­chen, indem man die mit ihm de­ko­rier­te Funktion namens „fac“ be­trach­tet. Diese wird im Code definiert und im Anschluss zuerst mit dem Wert „5“, dann mit dem Wert „-1“ auf­ge­ru­fen. Der Output sieht dann fol­gen­der­ma­ßen aus:

120
Traceback (most recent call last):
    File "<pyshell#17>", line 1, in <module>
        fak(-1)
    File "<pyshell#11>", line 6, in test
        raise Exception("Das Argument ist keine natürliche Zahl")
Exception: Das Argument ist keine natürliche Zahl

Zunächst sehen Sie die Zahl „120“, was der Fakultät von 5 ent­spricht. Für na­tür­li­che Zahlen funk­tio­niert die Fa­kul­täts­funk­ti­on also. Der Aufruf der Fa­kul­täts­funk­ti­on mit einer negativen Zahl führt al­ler­dings zu einem Fehler – und das liegt am Python Decorator! Da es sich bei einer negativen Zahl um keine na­tür­li­che Zahl handelt, soll die Fa­kul­täts­funk­ti­on nicht aus­ge­führt werden.

Zum Hauptmenü