11 Fortgeschrittene Funktionstechniken

Closures, Decorators und verwandte Konzepte sind Kernelemente der funktionalen Programmierung in Python. Sie ermöglichen sauberen, wiederverwendbaren und eleganten Code.

1 Closures

Ein Closure ist eine verschachtelte Funktion, die auf Variablen der äußeren Funktion auch nach deren Ausführung noch zugreifen kann. Dadurch entsteht ein erhaltener Zustand ohne Verwendung von Klassen.

1.1 Beispiel: Einfaches Closure

def greeting(name):
    # innere Funktion greift auf 'name' zu
    def say_hello():
        print(f'Hallo {name}!')
    return say_hello

greet = greeting('Stefan')
greet()  # Ausgabe: Hallo Stefan!

1.2 Beispiel: Closure mit Zustand

def counter():
    count = 0
    def increment():
        # Zugriff auf äußere Variable mit nonlocal
        nonlocal count
        count += 1
        return count
    return increment

c = counter()
print(c())  # 1
print(c())  # 2

2 Lambda-Funktionen

Lambda-Funktionen sind anonyme Funktionen, meist in einer Zeile geschrieben. Sie sind besonders praktisch in Kombination mit map, filter und anderen höherwertigen Funktionen.

Syntax:

lambda arguments: expression

Beispiel:

add = lambda x, y: x + y
print(add(3, 5))  # 8

3 Higher-Order Functions

Eine Higher-Order Function ist eine Funktion, die andere Funktionen entgegennimmt oder zurückgibt.

def apply_twice(func, value):
    return func(func(value))

print(apply_twice(lambda x: x + 1, 3))  # 5

4 map, filter, reduce

  • map(func, iterable): Wendet func auf jedes Element an.
  • filter(func, iterable): Filtert Elemente basierend auf Wahrheitswert von func.
  • reduce(func, iterable, initial): Akkumuliert alle Elemente zu einem einzigen Wert.
numbers = [1, 2, 3, 4, 5]

# map: Transformation
squared = list(map(lambda x: x**2, numbers))  # [1, 4, 9, 16, 25]

# filter: Bedingung
even = list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4]

# reduce: Akkumulation
from functools import reduce
summed = reduce(lambda acc, x: acc + x, numbers, 0)  # 15

5 Partial Functions

Vorkonfigurierte Funktionen mit functools.partial erlauben das Vorbelegen von Argumenten einer Funktion. Das ist nützlich, wenn man spezialisierte Varianten einer Funktion benötigt.

from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(2))    # 8
  • partial erstellt eine neue Funktion mit fixierten Parametern.
  • Hilfreich z. B. beim Konfigurieren von Callbacks oder API-Funktionen.

6 Function Factories (Funktions-Fabriken)

Funktionen, die andere Funktionen erzeugen – meist Closures. Ideal, wenn man eine Reihe verwandter Funktionen mit leicht unterschiedlichem Verhalten benötigt.

def make_multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

double = make_multiplier(2)
print(double(10))  # 20
  • factor bleibt in jeder zurückgegebenen Funktion erhalten.
  • Praktisch bei mathematischen Operationen, Filtern, usw.

7 Decorators

Ein Decorator erweitert das Verhalten einer Funktion, ohne ihren Code zu verändern. Er basiert auf Closures und Higher-Order Functions.

7.1 Einfacher Decorator

def my_decorator(func):
    def wrapper():
        print('Before the function runs.')
        func()
        print('After the function runs.')
    return wrapper

@my_decorator
def say_hello():
    print('Hello!')

say_hello()
  • @my_decorator ersetzt say_hello durch wrapper.
  • Ideal für Logging, Fehlerbehandlung, Zeitmessung etc.

7.2 Decorators mit Argumenten

Decorator-Funktionen können Argumente übernehmen, wenn sie flexibel konfiguriert werden sollen. Dazu ist eine zusätzliche Verschachtelung notwendig.

def logger(func):
    def wrapper(*args, **kwargs):
        print(f'Arguments: {args}, {kwargs}')
        return func(*args, **kwargs)
    return wrapper

@logger
def add(x, y):
    return x + y

add(3, 5)
  • *args und **kwargs fangen alle Argumente ab.
  • So bleiben Decorators universell einsetzbar.

7.3 Decorators mit Parametern

def speaker(volume):
    def decorator(func):
        def wrapper():
            print(f'[{volume.upper()}]')
            func()
        return wrapper
    return decorator

@speaker('quiet')
def whisper():
    print('psst...')

whisper()
  • speaker("quiet") gibt den eigentlichen Decorator zurück.
  • Mehr Flexibilität durch parametrisierte Dekoration.

7.4 Eingebaute Decorators

  • @staticmethod: Definiert eine Methode ohne self
  • @classmethod: Zugriff auf Klasse statt Instanz
  • @property: Erlaubt methodenartigen Zugriff auf Attribute
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return 3.14 * self._radius ** 2

7.5 Mehrere Decorators kombinieren

@decorator_one
@decorator_two
def some_function():
    pass
  • decorator_two wird zuerst angewendet, dann decorator_one.
  • Reihenfolge beachten, wenn Decorators interagieren.

7.6 Memoization mit lru_cache

from functools import lru_cache

@lru_cache
def fibonacci(n):
    if n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
  • lru_cache speichert bereits berechnete Ergebnisse im Cache.
  • Ideal für rekursive oder teure Berechnungen.

8 Zusammenfassung

Konzept Nutzen
Closures Zustand bewahren, Daten kapseln
Lambda Kürzere anonyme Funktionen
Higher-Order Funcs Flexible Funktionskomposition
map/filter/reduce Funktionale Verarbeitung von Listen
Partial Functions Vorbelegte Funktionen
Decorators Funktion erweitern ohne Quellcode zu ändern