ProgrammatiePython ontwikkelaar

Wat zijn Python-decorators met argumenten, hoe worden ze geïmplementeerd, waar is hun gebruik gerechtvaardigd en welke nuances zijn belangrijk bij het schrijven van eigen decorators met parameters?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord

Achtergrond van de vraag

Decorators zijn een van de krachtigste tools van Python, waarmee het gedrag van functies of methoden kan worden gemodificeerd. Soms is het nodig om niet alleen een functie "te omhullen", maar om de decorator in te stellen met behulp van parameters (argumenten). Deze gevallen komen voor bij logging, tijdcontroles, toegangsbeperkingen, enz.

Probleem

Gewone decorators nemen alleen één functie aan die moet worden omhuld. Wanneer het nodig is om parameters aan de decorator zelf door te geven, wordt de syntaxis ingewikkelder, wat vaak leidt tot fouten, vooral bij geneste functies en het doorgeven van *args/**kwargs.

Oplossing

Een decorator met parameters wordt geïmplementeerd via een hogere-orde functie: eerst wordt de externe "decoratieve" functie aangeroepen met argumenten, die de decorator creëert en teruggeeft, en de decorator ontvangt de functie en retourneert de wrapper:

def herhaal(n): def decorator(func): def wrapper(*args, **kwargs): resultaat = None for _ in range(n): resultaat = func(*args, **kwargs) return resultaat return wrapper return decorator @herhaal(3) def groet(naam): print(f"Hallo, {naam}!") groet("Python") # Output: Hallo, Python! (3 keer)

Belangrijkste kenmerken:

  • Een decorator met parameters wordt altijd geïmplementeerd via drievoudige genesteling van functies
  • De wrapper moet het resultaat retourneren en *args/**kwargs correct doorgeven
  • Vergeet niet functools.wraps te gebruiken om metadata op te slaan

Mischievige vragen.

Waarom kan een decorator met parameters niet op dezelfde manier worden geïmplementeerd als een gewone decorator?

Als je @decorator toepast, geeft Python de functie door als argument aan de decorator. Als je haakjes toevoegt (@decorator()), roept Python eerst de functie aan, en alleen het resultaat wordt geïnterpreteerd als een decorator.

def deco(func): # gewone decorator: @deco def deco_met_args(arg): # decorator met argument: @deco_met_args(arg)

Hoe verschillen decorators met argumenten fundamenteel van decorators zonder argumenten op het niveau van aanroep?

Decorators zonder argumenten ontvangen een functie als invoer, terwijl decorators met argumenten geen functie maar parameters ontvangen, en een decorator teruggeven.

Hoe pas je functools.wraps correct toe en waarom is dit belangrijk?

functools.wraps(func) in de wrapper behoudt de naam, documentatie-string en andere metadata van de originele functie; anders worden al deze gegevens vervangen in de wrapper, wat debugging en introspectie bemoeilijkt.

import functools def deco_met_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator

Typische fouten en anti-patronen

  • Ze vergeten dat er drie geneste functies nodig zijn, maken er slechts twee (of helemaal één), het resultaat zal geen decorator zijn
  • Ze geven *args/**kwargs niet door naar de wrapper
  • Ze verliezen meta-informatie van de functie door afwezigheid van functools.wraps

Voorbeeld uit het leven

Negatieve case

Een decorator is geïmplementeerd zonder rekening te houden met parameters of met een onjuist aantal geneste functies:

def log(niveau): def wrapper(func): # fout - wrapper moet dieper zijn print(f"Log: {niveau}") func() # De functie wordt niet geretourneerd als een decorator return wrapper @log("INFO") def actie(): print("Werk!")

Voordelen:

  • Ziet er eenvoudig uit

Nadelen:

  • De decorator werkt niet, de functie wordt aangeroepen tijdens het decoreren, niet bij het aanroepen van actie()

Positieve case

Gebruik van functools.wraps en correcte geneste functies:

import functools def timer(eenheden): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() resultaat = func(*args, **kwargs) eind = time.time() if eenheden == 'ms': duur = (eind - start) * 1000 else: duur = eind - start print(f"Duur: {duur:.4f} {eenheden}") return resultaat return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()

Voordelen:

  • Correcte structuur, gemakkelijk uit te breiden, schone logs

Nadelen:

  • Moeilijker te lezen, vooral voor beginners