Dekorator w Pythonie

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.

Czym jest wzorzec dekorator od strony formalnej

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:

undefined

Dekorator w praktyce – JAVA

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 :)

Dekoratory w Pythonie

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:

  • funkcja może przyjąć jako argument inną funkcję
  • wewnątrz funkcji można stworzyć kolejną funkcję

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!

Dekorator z argumentami

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:

  • run_only_between(7, 21) – zwróci funkcję real_decorator
  • zwrócona funkcja real_decorator nie ma nadpisanych argumentów więc przyjmie w argumencie funkcję którą ma dekorować
  • funkcja real_decorator zwróci funkcję wrapper która dodaje nową funkcjonalność, uruchamiając warunkowo dekorowaną funkcję

Przyznaj że sprytne:)

Nakładanie się dekoratorów

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.

Zmierzając do brzegu

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.

Dzięki za wizytę,
Mateusz Mazurek
Podziel się na:
    Facebook email PDF Wykop Twitter

2
Dodaj komentarz

avatar
1 Wątki
1 Odpowiedzi
1 Śledzący
 
Komentarz z największą liczbą reakcji
Najczęściej komentowany wątek
2 Komentarze autora
Mateusz M.Daroo Ostatnie komentarze autora

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subskrybuj  
Powiadom o
Daroo
Gość
Daroo

Jakies przyklady zastosowania dekoratorow?