Kodokon kodokon.com

Dekoratoren und Funktionen höherer Ordnung

Nutze Funktionen als First-Class-Objekte, um robuste, parametrierbare und transparente Dekoratoren zu schreiben.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

In Python ist eine Funktion ein Objekt wie jedes andere: Du kannst sie einer Variablen zuweisen, als Argument übergeben, zurückgeben. Eine Funktion höherer Ordnung nimmt eine Funktion entgegen oder gibt eine zurück - sorted(data, key=...) ist eine davon. Nimm noch Closures hinzu, bei denen eine innere Funktion die Variablen der umschließenden Funktion einfängt und sie über deren Rückkehr hinaus am Leben hält, und du hast alle Zutaten für Dekoratoren.

PYTHON
def make_multiplier(factor):
    def multiply(value):
        return value * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10))  # 20 30
Eine Closure: multiply fängt factor ein

Ein Dekorator ist eine Funktion, die eine Funktion entgegennimmt und eine andere zurückgibt - meist einen wrapper, der Verhalten um den Aufruf herum ergänzt. Die Syntax @timed über def slow_sum ist reiner Zucker für slow_sum = timed(slow_sum). Der Wrapper akzeptiert *args, kwargs, um gegenüber der Signatur agnostisch zu bleiben, und gibt das Ergebnis der dekorierten Funktion zurück** - das zu vergessen ist ein klassischer Fehler, der jeden Rückgabewert in None verwandelt.

PYTHON
import functools
import time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        delay = time.perf_counter() - start
        print(f"{func.__name__}: {delay:.4f}s")
        return result
    return wrapper

@timed
def slow_sum(n):
    return sum(range(n))

print(slow_sum(1_000_000))
Ein Zeitmess-Dekorator, produktionsreif

Um einen Dekorator zu parametrieren (@repeat(times=3)), brauchst du eine Ebene mehr: eine Factory, die die Parameter entgegennimmt und den Dekorator zurückgibt, der seinerseits den Wrapper zurückgibt. Drei verschachtelte Funktionen - das ist der Preis der Flexibilität. Wenn der Zustand komplex wird, ist eine Klasse mit einer __call__-Methode oft lesbarer. Reserviere Dekoratoren für Querschnittsbelange: Logging, Caching, Retry, Zugriffskontrolle. Ein Dekorator, der die Geschäftslogik verändert, verbirgt das Wesentliche vor dem Leser.

PYTHON
import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello {name}")

greet("Ada")
Parametrierter Dekorator: Factory, Dekorator, Wrapper

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Wozu ist die Syntax @timed über def slow_sum äquivalent?
    • timed(slow_sum())
    • slow_sum = timed(slow_sum)
    • slow_sum.timed = True
  2. Welches Problem löst @functools.wraps?
    • Es beschleunigt den Aufruf der dekorierten Funktion
    • Es bewahrt Name und Docstring der dekorierten Funktion
    • Es macht den Dekorator mit Klassenmethoden kompatibel
  3. Was gibt bei @repeat(times=3) der Aufruf repeat(times=3) zurück?
    • Das Ergebnis der dekorierten Funktion
    • Einen Dekorator, bereit, die Funktion entgegenzunehmen
    • Direkt den finalen Wrapper
    • Eine Liste von drei Ergebnissen