Cześć,
temat wzorców projektowych to temat rzeka:) przyglądając się mu można jednak łatwo zauważyć że dzielą się ona na te które przyjęły się szerzej jak i te które, no w sumie istnieją, ale nie słychać o nich za wiele.
Jednym z takich szumnych wzorców jest wzorzec dekorator.
Omawiany przez nas wzorzec należy do grupy wzorców strukturalnych, a więc takich które opisują struktury powiązanych ze sobą obiektów.
Jego działanie opiera się na opakowywaniu oryginalnej klasy, nowa klasą „dekorującą”. Czyli po prostu przekazuje się oryginalny obiekt jako parametr konstruktora dekoratora, metody dekoratora wywołują metody oryginalnego obiektu i dodatkowo implementują nową funkcję.
Aby zachować pełnie informacji formalnych – oto diagram klas tego wzorca:
Taki chyba najbardziej znanym przykładem użycie dekoratora w Javie jest jest klasa FileInputStream – posiada ona kilka dekoratorów i dodaje się do niej kolejne funkcjonalności w taki sposób:
FileInputStream fis = new FileInputStream("/objects.gz");
Tworzymy strumień, ale chcemy by był on buforowany, więc:
BufferedInputStream bis = new BufferedInputStream(fis);
Wszystko super, ale nasz plik jest skompresowany, więc dodajemy:
GzipInputStream gis = new GzipInputStream(bis);
I plik zawiera zserializowane obiekty Javowe, wiec dodajemy:
ObjectInputStream ois = new ObjectInputStream(gis);
No i finalnie możemy użyć naszego strumienia:
MyObj ungzipedDeserializedObj = (MyObj) ois.readObject();
Odczytując z niego jakiś obiekt.
Warto zauważyć że kolejne opakowywanie wcześniej stworzonego obiektu dodawało mu kolejne możliwości, odpowiednio – pierw zapewniało buforowanie odczytu, potem dekompresowało a na końcu jeszcze deserializowało. Pełna magia :)
Dobra, ale dość przygód z Javą, przejdźmy do Python’a.
Dekoratory w Pythonie opierają się przede wszystkim na dwóch założeniach:
Ale zerknijmy na przykład:
1 2 | def say_something(): print("Hello!!") |
Ten potężny kawałek kodu nie robi nic innego jak printuje słowo „Helo!!”. A teraz ten kawałek kodu:
1 2 3 4 5 6 7 8 9 | from datetime import datetime def disable_at_night(func): def wrapper(): if 7 <= datetime.now().hour < 22: func() else: pass return wrapper |
Tutaj już jest ciekawiej – definiujemy funkcję o nazwie disable_at_night która przyjmie jako argument inną funkcje i zwróci jeszcze inną funkcję. Zauważ że w środku stworzonej przez nas funkcji jest funkcja wrapper i to właśnie ona jest wracana… Ale w niej jest wykonywana funkcja przekazana jako argument. Przeanalizuj to dokładnie, bo właśnie w tej chwili stworzyliśmy dekorator.
I nasz cały kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from datetime import datetime def say_something(): print("Hello!!") def disable_at_night(func): def wrapper(): if 7 <= datetime.now().hour < 22: func() else: pass return wrapper disable_at_night(say_something) |
Który spowoduje wykonanie się funkcji say_something tylko jeśli aktualna godzina jest pomiędzy 7mą a 22gą – czyli w dzień.
Python pozwala na pewien mały „syntax sugar” i daje możliwość zamienienia wywołania:
disable_at_night(say_something)()
na
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from datetime import datetime def disable_at_night(func): def wrapper(): if 7 <= datetime.now().hour < 22: func() else: pass return wrapper @disable_at_night # <--- def say_something(): print("Hello!!") say_something() # <--- |
Muszę przyznać że składnia ala adnotacja jest bardzo wygodna!
W Pythonie możemy stworzyć dekorator z argumentami. Weźmiemy teraz nasz przykład z góry i przepiszemy nasz dekorator by był bardziej generyczny – pozwolimy przekazać godziny w argumencie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from datetime import datetime def run_only_between(_from=7, _to=22): def real_decorator(func): def wrapper(): if _from <= datetime.now().hour < _to: func() else: pass return wrapper return real_decorator @run_only_between(7, 21) def say_something(): print("Hello!!") say_something() # <--- |
Poprzednio funkcja nazywała się disable_at_night – teraz stała się bardziej elastyczna, co spowodowało że dostała inną nazwę – run_only_between.
Zauważ że nasz dekorator wzbogacił się o jeszcze jedną funkcję w sobie! Mamy już funkcję w funkcji w funkcji :)
I teraz tak, wykonując funkcję say_something stos wywołań wygląda tak:
Przyznaj że sprytne:)
Zerknij na taki kawałek:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from datetime import datetime def log_before_and_after(func): def wrapper(): print("before") result = func() print("after") return result return wrapper def run_only_between(_from=7, _to=22): def real_decorator(func): def wrapper(): if _from <= datetime.now().hour < _to: func() else: pass return wrapper return real_decorator @log_before_and_after @run_only_between(7, 22) def say_something(): print("Hello!!") say_something() # <--- |
Dodaliśmy tu jeszcze jedna funkcję która jest użyta jako dekorator – dodaje logowanie przed i po wykonaniu funkcji. Oczywiście dekoratory są wykonywane od góry, więc nawet jeśli run_only_between wytnie nam wywołanie naszej funkcji, to „before” i „after” nadal się pojawi.
Mi osobiście dekoratory w Pythonie bardzo się podobają – są sprytne i takie, hmm, funkcjonalne? Tak, to chyba dobre słowo. Pisząc „funkcjonalne” mam na myśli że można naprawdę wiele rzeczy nimi załatwić nie niszcząc przy tym architektury.
Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More
Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More
Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More
Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More
Pokaż komentarze
Jakies przyklady zastosowania dekoratorow?
Cześć, z dwa były w samym artykule:) możesz użyć np do mierzenia czasu wykonania się funkcji. Ogólnie wszedzie tam gdzie chcesz rozszerzyć funkcję o dodatkowe możliwości :)
Trzeba pamiętać, że Pythonowy dekorator nie ma nic wspólnego z wzorcem dekorator.
Z tym "nic" to bym się kłócił, bo Pythonowy nadal opakowuje i rozszerza działanie obiektu:P
Nie no pewnie ;) Ale tak chciałem podkreślić, że samo rozszerzanie obiektu co umożliwia dodanie tzw. dekoratora w Pythonie nie sprawia, że od razu jest spełniony wzorzec projektowy ;-))
Tak żeby osoby co przeczytają ten artykuł od razu nie myślały, że to jedno i to samo
Aaaaa no to tak, to zgadzam się :D
Dziękuję za pomoc w nauce o dekoratorach. Pozdrawiam
Cieszę się że pomogłem!
Cześć Mateusz,
Mi się dekoratory też podobają choć czasem trudno jest to sobie wyobrazić jak na przykład te wywołania funkcji ,w funkcji ,w funkcji.
Nie mam za dużo doświadczenia z dekoratorami ale pisałem w poście na moim blogu o dekoratorze @functools.lru_cache(). Jak chcesz to przeczytaj, jest po angielsku -
https://mstem.net/optimization-code-python-1/
Poza tym to nie wiedziałem że dekorator to wzorzec projektowy. Czy mógłbyś napisać o innych wzorcach projektowych w języku Python?
Cześć.