Dekoratory
Funkcje, które opakowują inne funkcje i dodają im zachowanie, np. logowanie lub sprawdzanie uprawnień. Zapis @nazwa.
Przykład
#import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Wywołuję {func.__name__}{args}")
result = func(*args, **kwargs)
print(f"{func.__name__} zwróciła {result!r}")
return result
return wrapper
@log_call
def award(name, exp):
"""Przyznaje punkty doświadczenia."""
return f"{name}: +{exp} EXP"
award("Ania", 15)
print(award.__name__, "|", award.__doc__)Definicja i zastosowanie
#Dekorator to funkcja, która przyjmuje inną funkcję i zwraca jej rozszerzoną wersję. Zapis @log_call nad definicją funkcji to skrót od award = log_call(award). Mechanizm działa, bo w Pythonie funkcje są zwykłymi obiektami: można je przekazywać jako argumenty i zwracać z innych funkcji.
Typowy dekorator definiuje w środku funkcję opakowującą (wrapper). Przyjmuje ona dowolne argumenty przez *args i **kwargs, robi coś przed wywołaniem oryginału, wywołuje go, robi coś po i zwraca jego wynik. W ten sposób dodasz logowanie, pomiar czasu, sprawdzanie uprawnień czy ponawianie prób bez zmieniania samych funkcji.
Wrapper zasłania nazwę i docstring oryginalnej funkcji: bez dodatkowego kroku award.__name__ zwróciłoby "wrapper". Dekorator @functools.wraps(func) nałożony na wrapper kopiuje te informacje, dlatego warto go używać zawsze. Dekorator z własnymi parametrami, np. @require_level(5), wymaga jeszcze jednego poziomu: zewnętrzna funkcja przyjmuje parametry i zwraca właściwy dekorator.
Kilka dekoratorów nad jedną funkcją działa od dołu: najbliższy definicji opakowuje ją jako pierwszy. Biblioteka standardowa ma wiele gotowych dekoratorów, np. @property, @classmethod, @dataclass czy @functools.lru_cache.
Składnia
#import functools
def dekorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wynik = func(*args, **kwargs)
return wynik
return wrapper
@dekorator
def funkcja():
...Więcej przykładów
#import functools
def require_level(minimum):
def decorator(func):
@functools.wraps(func)
def wrapper(player, *args, **kwargs):
if player["level"] < minimum:
return f"{player['name']}: potrzebny poziom {minimum}"
return func(player, *args, **kwargs)
return wrapper
return decorator
@require_level(5)
def enter_world(player, world):
return f"{player['name']} wchodzi do świata {world}"
print(enter_world({"name": "Ania", "level": 7}, 3))
print(enter_world({"name": "Kuba", "level": 2}, 3))def bold(func):
def wrapper():
return f"**{func()}**"
return wrapper
def exclaim(func):
def wrapper():
return f"{func()}!"
return wrapper
@bold
@exclaim
def title():
return "Nowy poziom"
print(title())Zapis jest równoważny title = bold(exclaim(title)), więc wykrzyknik dodaje się przed pogrubieniem.
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def check_answer(answer):
return answer == "B"
for answer in ["A", "B", "C"]:
check_answer(answer)
print(check_answer.calls)Dobre praktyki
#- Zawsze dodawaj
@functools.wraps(func)do funkcji opakowującej. Zachowasz nazwę i docstring oryginału, co ułatwia debugowanie i czytanie komunikatów błędów. - Wrapper powinien przyjmować
*args, **kwargsi zwracać wynik oryginału. Bezreturnkażda udekorowana funkcja zacznie zwracaćNone. - Kod samego dekoratora uruchamia się raz, przy definiowaniu funkcji, a wrapper przy każdym wywołaniu. To, co ma działać przy każdym wywołaniu, umieść wewnątrz wrappera.
Powiązane hasła
#- defDefiniuje funkcję: nazwany blok kodu z parametrami, który można wywoływać wielokrotnie.
- *args i **kwargsZbieranie dowolnej liczby argumentów pozycyjnych w krotkę i nazwanych w słownik oraz rozpakowywanie kolekcji przy wywołaniu.
- functoolsNarzędzia do pracy z funkcjami: zapamiętywanie wyników (lru_cache), częściowe wywołania (partial) i zwijanie kolekcji (reduce).
- @propertyZamienia metodę w atrybut: wartość jest wyliczana przy odczycie, a opcjonalny setter kontroluje zapis.
- global i nonlocalPozwalają funkcji zmieniać zmienne z zasięgu globalnego lub funkcji otaczającej.
- lambdaKrótka funkcja anonimowa zapisana w jednym wyrażeniu.
Widzisz błąd albo brakuje przykładu? Napisz do nas.