Nasza strona używa cookies. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Dekoratory w Pythonie - czemu warto z nich korzystać

Rhea Moutafis MBA Fellow / Collège des Ingénieurs
Sprawdź, jak używać dekoratorów w Pythonie i dowiedz się, jak mogą Ci pomóc w codziennej pracy.
Dekoratory w Pythonie - czemu warto z nich korzystać

Gdybyśmy mieli wymienić jedną rzecz, dzięki której Python odnosi tak niewiarygodny sukces, to byłaby nią czytelność. Wszystko właściwie kręci się wokół tego. Jeśli kod jest mało czytelny, trudno go utrzymać, a to staje się problematyczne również dla początkujących. Gdy taki ktoś widzi nieczytelny kod, to prawdopodobnie nie podejmie nawet próby napisania własnego. 

Python był już czytelny i przyjazny dla użytkowników jeszcze zanim pojawiły się w nim dekoratory. Jednak wraz z pojawieniem się nowych potrzeb, w Pythonie zaczęły pojawiać się nowe funkcje. Oczywiście bez wprowadzania zbędnego bałaganu, co mogło skutkować nieczytelnym kodem.

I to właśnie dekoratory są doskonałym przykładem znakomicie wdrożonej funkcji. Co prawda, potrzeba czasu, aby się w tym połapać, ale naprawdę warto wgryźć się w temat. Kiedy tylko zaczniesz ich używać, szybko zauważysz, że nie komplikują one zbytnio sprawy. Kod będzie wyglądał na atrakcyjny i starannie skonstruowany


Zanim przejdziemy do clou sprawy... funkcje wyższego rzędu

W dużym skrócie. Dekoratory to świetny sposób na poradzenie sobie z funkcjami wyższego rzędu, dlatego przyjrzyjmy się im!


Funkcje zwracające inne funkcje

Powiedzmy, że mamy do czynienia z funkcją, greet(), która wita jakikolwiek przekazany do niej obiekt. Mamy też kolejną funkcję simon(), która wstawia Simon we właściwych miejscach. Jak jednak połączyć obie te funkcje? Zastanów się nad tym przez chwilę, zanim spojrzysz na kod poniżej. 

def greet(name):
    return f"Hello, {name}!"
def simon(func):
    return func("Simon")
simon(greet)


W rezultacie otrzymamy Hello, Simon!. Mam nadzieję, że jest to dla Ciebie zrozumiałe!

Oczywiście, moglibyśmy również wywołać greet("Simon"), jednak cały szkopuł tkwi w tym, że możemy chcieć umieścić "Simon" w wielu innych funkcjach. Gdybyśmy nie używali "Simon", ale czegoś bardziej skomplikowanego, to moglibyśmy zaoszczędzić wiele linijek kodu, pakując je do takiej funkcji, jak simon()


Funkcje w innych funkcjach?

Owszem! Możemy tworzyć funkcje wewnątrz innych funkcji. Dekoratory potrafią nawet coś takiego! Natomiast bez nich wygląda to tak:

def respect(maybe):
    def congrats():
        return "Congrats, bro!"
    def insult():
        return "You're silly!"
    if maybe == "yes":
        return congrats
    else:
        return insult


Funkcja respect() zwraca funkcję; respect("yes") zwraca funkcję congrats; respect("brother") (albo jakiś inny argument zamiast "brother") zwraca funkcję insult. Aby wywołać te funkcje, wprowadź respect("yes")() oraz respect("brother")(), tak jak normalną funkcję.


Wszystko jasne? A więc jesteś gotowy na dekoratory!


ABC dekoratorów Pythona


Funkcje z symbolem @

Przyjrzyjmy się połączeniu dwóch poprzednich rozwiązań: funkcji wykorzystującej inną funkcję oraz tej, która ją definiuje. Brzmi groźnie? Spójrz na to:

def startstop(func):
    def wrapper():
        print("Starting...")
        func()
        print("Finished!")
    return wrapper
def roll():
    print("Rolling on the floor laughing XD")
roll = startstop(roll)


Ostatnia linia pokazuje nam, że nie musimy już wywoływać startstop(roll)(), wystarczy tylko roll(). Czy wiesz jaki jest wynik takiego wywołania? Nie jesteś pewien? Spróbuj sam! Dobrą alternatywą może być wstawienie tego zaraz po zdefiniowaniu funkcji startstop().

@startstop
def roll():
    print("Rolling on the floor laughing XD")


Działa to tak samo, ale łączy roll() ze startstop() już na samym początku.


Nieco więcej elastyczności

Dlaczego jest to takie przydatne? Czy nie zajmuje dokładnie tyle samo linijek kodu, co wcześniej?

W tym przypadku owszem, zajmuje. Ale kiedy mamy do czynienia z nieco bardziej skomplikowanymi rzeczami, staje się to naprawdę użyteczne. Możesz po prostu przenieść wszystkie dekoratory (czyli def startstop()) do własnego modułu. Krótko mówiąc, umieszczasz je w pliku o nazwie decorators.py i zapisujesz w swoim głównym pliku coś takiego:

from decorators import startstop
@startstop
def roll():
    print("Rolling on the floor laughing XD")


W gruncie rzeczy można to zrobić bez użycia dekoratorów. Jednak z ich pomocą twoje życie staje się o wiele prostsze, ponieważ nie musisz już walczyć z zagnieżdżonymi funkcjami, czy niekończącym się liczeniem nawiasów. 

Możliwe jest również zagnieżdżenie dekoratorów:

from decorators import startstop, exectime
@exectime
@startstop
def roll():
    print("Rolling on the floor laughing XD")


Zauważ, że nie zdefiniowaliśmy jeszcze exectime(), ale zobaczymy to w następnej części tekstu. Jest to funkcja, która może mierzyć, jak długo trwa dany proces w Pythonie.

Takie zagnieżdżenie będzie równoważne z poniższym:

roll = exectime(startstop(roll))


3..2..1.. odliczanie nawiasów rozpoczęte! A teraz wyobraź sobie, że zagnieżdżone było pięć czy sześć takich funkcji, jedna po drugiej. Czy więc zapis dekoratora nie byłby o wiele łatwiejszy do odczytania niż ten zagnieżdżony chaos?

Możesz także używać dekoratorów z funkcjami, które przyjmują argumenty. Teraz wyobraź sobie, że w powyższej linii znajduje się kilka argumentów i cały ten chaos nabierze mocy. Dekoratory dbają więc tutaj o ład i porządek.

Co więcej, do dekoratorów możesz dodać również argumenty, np. @mydecorator(argument). Jasne, możesz to zrobić bez nich, ale nie chciałbym być Tobą, gdy za trzy tygodnie będziesz próbował odnaleźć się w kodzie bez dekoratorów.


Aplikacja: czyli gdzie dekoratory spijają śmietankę

Jeśli już przekonałem Cię, że dekoratory po stokroć ułatwiają życie, przyjrzyjmy się klasycznym przykładom, które pokazują, dlaczego dekoratory są niezastąpione.


Czas wykonania

Załóżmy, że mamy funkcję o nazwie wastetime() i chcemy określić, jak długo trwa jej wykonanie. Nic prostszego. Użyj dekoratora! Kilkanaście linijek kodu i gotowe!

import time
def measuretime(func):
    def wrapper():
        starttime = time.perf_counter()
        func()
        endtime = time.perf_counter()
        print(f"Time needed: {endtime - starttime} seconds")
    return wrapper
@measuretime
def wastetime():
    sum([i**2 for i in range(1000000)])
wastetime()


Kolejny plus, możesz używać measuretime() na tylu funkcjach, ilu tylko chcesz.


Spowalnianie kodu

Może się zdarzyć, że nie będziesz chciał wykonać kodu natychmiast, ale odczekać chwilę. Wtedy właśnie z pomocą nadchodzi dekorator spowalniający:

import time
def sleep(func):
    def wrapper():
        time.sleep(300)
        return func()
    return wrapper
@sleep
def wakeup():
    print("Get up! Your break is over.")
wakeup()


Wywołanie funkcji wakeup() pozwala Ci na 5-minutową przerwę, po której Twoja konsola przypomina Ci o powrocie do pracy.


Testy i debugowanie

Załóżmy, że posiadasz mnóstwo funkcji, które wywołujesz na różnych etapach, po czym tracisz kontrolę nad tym, co i kiedy jest wywoływane. Z pomocą prostego dekoratora, do każdej definicji funkcji możesz wprowadzić więcej przejrzystości i czytelności. Tak jak poniżej:

def debug(func):
    def wrapper():
        print(f"Calling {func.__name__}")
    return wrapper
@debug
def scare():
    print("Boo!")
scare()


Tutaj znajdziesz o wiele bardziej rozbudowany przykład. Zauważ jednak, że aby dobrze go zrozumieć będziesz musiał dowiedzieć się nieco więcej o dekorowaniu funkcji z argumentami. Mimo to warto przeczytać!


Ponowne wykorzystanie kodu

Właściwie co tu więcej dodać. Sprawa jest prosta jak drut. Jeśli już zdefiniowałeś funkcję decorator() , możesz rozsypać @decorator jak confetti w swoim kodzie. Szczerze powiedziawszy, nie wiem, czy da się to jeszcze prościej ująć.


Logowanie

Kiedy mamy do czynienia z funkcjami, do których dostęp możliwy jest w przypadku zalogowania się użytkownika, to również tutaj dekoratory przychodzą z pomocą. Zasada tworzenia jest równie prosta jak poprzednie. Najpierw definiujesz funkcję taką jak login_required(), a przed każdą definicją funkcji, która wymaga zalogowania się, wyskakujesz z @login_required. Banalnie proste, prawda?


Lukier składniowy czyli dlaczego Python to taki słodki owoc?

Nie jest tak, że uważam Pythona za najsłodszy owoc programowania, czy też nie uznaję innych, alternatywnych, tam, gdzie należałoby je uznać. Jednak Python jest tak wdzięczny w obsłudze, i jednocześnie łatwy do przyswojenia, że nawet nie będąc informatykiem z wykształcenia, jesteś w stanie sprawić, że coś w Pythonie zadziała.

Jeśli C++ jest pomarańczą, to Python jest tutaj ananasem: podobna wartość odżywcza, ale trzy razy słodszy. Dekoratory to tylko jeden ze składników całej słodkiej mieszanki Pythona. 

Mam jednak nadzieję, że zrozumieliście dlaczego ten słodki składnik jest tak ważny. Lukier składniowy, który da  Ci nieco więcej przyjemności życia. Bez skutków ubocznych dla zdrowia! Może poza wzrokiem ciagle wlepionym w ekran. 

Życzę ci samych słodkich kodów!


Oryginał tekstu w języku angielskim możesz przeczytać tutaj.

Rozpocznij dyskusję

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 160 tysiącami naszych czytelników

Dowiedz się więcej