Wyjaśnienie dekoratorów Pythona: od @property do logiki ponownej próby w 15 minut (2026)
Dekoratory to funkcja oddzielająca „zna Pythona” od „płynnie czyta Pythona”. Wyglądają magicznie, ale są mechaniczne: @dekorator w funkcji to dokładnie f = dekorator(f). To wszystko. Cała reszta w tym samouczku polega na tym, że jedna linia ma różne kształty.
Teoria jednej linii
Kiedy piszesz:
Python przekształca to w:
To jest cała mechanika. @nazwa_dekoratora powyżej def uruchamia nazwa_dekoratora = nazwa_dekoratora(nazwa_dekoratora) po definicji. Jeśli to zinternalizujesz, dekoratorzy przestaną być tajemniczy na zawsze.
Pisanie pierwszego dekoratora (60 sekund)
Dekorator to dowolny obiekt wywoływalny, który przyjmuje funkcję i zwraca funkcję. Typowy kształt:
*args, **kwargs w opakowaniu jest wzorcem obronnym — pozwala dekoratorowi pracować na dowolnej funkcji niezależnie od podpisu. Pomiń to, a otrzymasz komunikat „TypeError: wrapper() przyjmuje 0 argumentów pozycyjnych, ale podano 2” w chwili, gdy ktoś ozdobi funkcję parametrami.
Poprawka @functools.wraps
Jeden subtelny błąd: po dekorowaniu add.__name__ jest `opakowaniem'', a nie `dodawaniem''. Dokumenty i wskazówki dotyczące typów również znikają. Poprawka polega na jednym imporcie:
Zasada: każdy dekorator, który napiszesz, powinien używać `@functools.wraps(fn)`. Kosztuje jedną linię i zapobiega całej kategorii problemów związanych z debugowaniem (introspekcja, urządzenia pytest, formatery logging, pickle — wszystko bez tego psuje się cicho).
Cztery dekoratory, które zobaczysz w każdej bazie kodu
1. @właściwość — obliczane atrybuty
@właściwość sprawia, że metoda jest wywoływalna tak, jakby była atrybutem. Idealny do wartości pochodnych tylko do odczytu. Istnieje również ścieżka zapisu @area.setter, ale 90% zastosowań @property jest tylko do odczytu.
2. @metoda statyczna i @metoda klasy
@classmethod pobiera cls (samą klasę) i jest używane dla alternatywnych konstruktorów. @staticmethod nie oferuje niczego specjalnego i jest zwykłą funkcją, która po prostu żyje w przestrzeni nazw klasy.
3. @functools.cache — zapamiętywanie
Jeden z najpotężniejszych dekoratorów stdlib. Automatycznie zapamiętuje funkcję na podstawie jej argumentów. Wymaga, aby wszystkie argumenty mogły być mieszane (żadnych list/dykt jako argumentów — użyj krotek).
4. @dataclasses.dataclass — eliminacja szablonów
@dataclass odczytuje adnotacje typu na poziomie klasy i automatycznie generuje __init__, __repr__, __eq__ i (przy frozen=True) __hash__. To największa pojedyncza poprawa jakości życia w Pythonie 3.7+.
Pisanie dekoratora @retry (przykład z życia wziętego)
Dekorator, który ponawia próbę wykonania funkcji w przypadku niepowodzenia z wykładniczym wycofywaniem:
Zwróć uwagę na trzy zagnieżdżone funkcje: retry akceptuje argumenty konfiguracyjne i zwraca dekorator; dekorator akceptuje funkcję i zwraca wrapper; wrapper uruchamia pętlę ponawiania prób. Ten trójwarstwowy wzór wygląda tak, jak każdy dekorator z argumentami — zapamiętaj kształt, a nie konkretny przykład.
Dekoratory oparte na klasach (rzadkie, ale przydatne)
Dekorator jest dowolny. Klasy z __call__ też działają:
Używaj, gdy dekorator musi utrzymać stan, który przetrwa pojedyncze wywołanie (licznik, pamięć podręczna, segment z limitem szybkości).
Typowe pułapki
1. Zapomnij o `@functools.wraps` — introspekcja przerywa ciszę.
2. Zmienne domyślne argumenty w samym dekoratorze — tutaj również pojawia się klasyczny błąd def wrapper(x, cache=[]).
3. Dekorowanie metod bez akceptowania „ja” — pamiętaj, „ja” to tylko argument pozycyjny; *args, **kwargs łapie to dobrze.
4. Kolejność ma znaczenie podczas układania: @a\n@b\ndef f() to f = a(b(f)). Czytaj stosy od dołu do góry.
Ćwicz
Napisz dekorator @timing, który wypisuje czas działania opakowanej funkcji, a następnie zastosuj go do funkcji obliczającej sumę(zakres(10_000_000)). Cały dekorator składa się z 8 linii z functools.wraps + time.perf_counter.
Kiedy już zinternalizujesz, że @x to f = x(f), dekoratory staną się jedną z najbardziej produktywnych funkcji Pythona. Są wszędzie w prawdziwych bazach kodu: trasy Flask/FastAPI (@app.route), urządzenia pytest (@pytest.fixture), widoki Django (@login_required), walidatory Pydantic (@field_validator). Nauczenie się ich samodzielnego pisania odblokowuje możliwość czytania każdego z nich.
Następny krok: 10 praktycznych lekcji dekoratora — zbuduj od podstaw @retry, @memoise, @rate_limit i @requires_auth od podstaw z prawdziwym Pythonem uruchomionym w Twojej przeglądarce.