Mikuláš je tu! Získaj 90 % extra kreditov ZADARMO s promo kódom CERTIK90 pri nákupe od 1 199 kreditov. Len do nedele 7. 12. 2025! Zisti viac:
NOVINKA: Najžiadanejšie rekvalifikačné kurzy teraz s 50% zľavou + kurz AI ZADARMO. Nečakaj, táto ponuka dlho nevydrží! Zisti viac:

17. diel - Dekorátory v Pythone

V predchádzajúcom cvičení, Riešené úlohy k 14.-15. lekcii OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V nasledujúcom tutoriáli objektovo orientovaného programovania v Pythone sa podrobnejšie pozrieme na dekorátory. Vysvetlíme si, že dekorátory sú nástroj, ktorý nám umožňuje ľahko pridať nové správanie funkciám, metódam alebo triedam bez nutnosti meniť ich pôvodný kód.

Dekorátory v Pythone

Predstavme si, že pracujeme na projekte, kde máme v kóde veľké množstvo metód. Zrazu zistíme, že všetky tieto metódy potrebujú vykonávať niečo navyše – napríklad pridať k svojmu výstupu čas ich volania. Môžeme začať prepisovať jednu metódu po druhej a ručne túto funkcionalitu doplniť. To by ale bolo veľmi zdĺhavé a náchylné k chybám. Namiesto toho môžeme využiť dekorátory, ktoré tento problém vyriešia elegantne a bez nutnosti meniť pôvodný kód. V praxi sa dekorátory často používajú napríklad na logovanie, autorizáciu alebo validáciu vstupov.

Základná syntax dekorátorov

Základné princípy si vyskúšame na jednoduchej funkcii write_to_diary(), ktorú následne oddekorujeme:

def write_to_diary(message):
    print(message)

Naša funkcia momentálne iba vypisuje správu, ktorú jej odovzdáme. Čo ale urobiť, ak chceme ku každej správe automaticky pridať dátum a čas? Namiesto toho, aby sme upravili pôvodný kód, použijeme dekorátor. Ten nám umožní zmenu vykonať rýchlo a elegantne bez toho, aby sme zasahovali do pôvodnej funkcie. Dekorátor potom môžeme využiť aj pri ďalších funkciách, ktoré niečo vypisujú a pripojiť tak aj k ich výpisom časovú pečiatku:

from datetime import datetime

def add_timestamp(decorated_function):
    def modify_message(message):
        time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return decorated_function("[" + time + "] " + message)
    return modify_message

@add_timestamp
def write_to_diary(message):
    print(message)

write_to_diary("Sign up for Python OOP course")

Vytvorili sme funkciu add_timestamp(), ktorú používame ako dekorátor. Dekorátor totiž v tejto podobe nie je nič iné ako obyčajná funkcia, ktorá v parametri prijme inú funkciu, upraví jej správanie a následne ju vráti. Fungovanie je zaistené pomocou vnorenej funkcie modify_message() vo vnútri dekorátora, ktorá obaľuje tú pôvodnú tak, že pri jej zavolaní pridáva k pôvodnej správe ešte aktuálny čas. Po vytvorení nášho dekorátora "ozdobíme" pôvodnú funkciu pridaním znaku @ a názvu dekorátora nad deklaráciu upravovanej funkcie. V tejto chvíli pri zavolaní write_to_diary() dostaneme vždy už oddekorovaný výstup, teda reťazec s aktuálnym časom.

Funkcie ako objekty prvej triedy

Zrejme nás prekvapil spôsob, ako s funkciami pracujeme. Naraz ich píšeme bez zátvoriek a odovzdávame ich ako parametre ďalším funkciám! Toto je možné vďaka tomu, že funkcie sú v Pythone takzvané objekty prvej triedy. Znamená to, že s nimi môžeme zaobchádzať rovnako ako s klasickými objektmi. Môžeme ich teda odovzdávať ako parameter iným funkciám a môžeme ich tiež uložiť do premenných. Keď týmto spôsobom pracujeme s funkciou, píšeme jej názov bez zátvoriek – nejde totiž o jej volanie, ale o odovzdávanie funkcie ako takej. Pozrime sa na jednoduchý príklad:

def write_data():
    return "Writing data to the database."

print(write_data())
print(write_data)

Máme jednoduchú funkciu, ktorá vracia textový reťazec. V prípade, že zavoláme print(write_data()), vypíše sa nám do konzoly reťazec vrátený touto funkciou. Funkciu sme teda zavolali. Keď ale budeme chcieť vytlačiť funkciu samotnú, teda bez zátvoriek, dostaneme len informáciu o objekte reprezentujúcom túto funkciu, konkrétne jej pamäťovú adresu:

Konzolová aplikácia
Writing data to the database.
function write_data at 0x000002BA268B04A0

Dekorovanie funkcií s viacerými parametrami

V úvodnom príklade dekorátor pracoval s funkciou, ktorá prijímala iba jeden parameter, správu k výpisu. Ukážme si, ako vytvoriť dekorátor, ktorý dokáže pracovať s funkciami, ktoré majú rôzny počet parametrov V takýchto prípadoch použijeme parametre *args a **kwargs.

Parameter *args uchováva kolekciu všetkých pozičných argumentov, zatiaľ čo **kwargs obsahuje kolekciu všetkých kľúčových argumentov. Pomocou týchto konštrukcií môžeme k odovzdávaným argumentom pristupovať bez ohľadu na ich počet či typ. Tento spôsob práce s parametrami zaisťuje dekorátorom veľkú flexibilitu.

Pozrime sa na príklad funkcie pre výpočet celkovej ceny objednávky. Pomocou dekorátora zaistíme, že k základnej cene bude automaticky pridaná daň:

def add_tax(decorated_function):
    def add_tax_to_result(*args, **kwargs):
        price = decorated_function(*args, **kwargs)
        price_with_tax = price + (args[0] * args[1]) * 0.21
        return f"Total order price with tax is: {price_with_tax:.2f} EUR"
    return add_tax_to_result

@add_tax
def record_order(price_per_item, quantity, shipping=0, discount=0):
    return price_per_item * quantity + shipping - discount

print(record_order(200, 3, shipping=50, discount=100))

Vďaka *args a **kwargs je náš dekorátor flexibilný a dokáže dekorovať akúkoľvek funkciu bez ohľadu na počet a typ jej argumentov. V našom kóde sme postupovali rovnako ako v predchádzajúcom príklade. Opäť sme v dekorátore vytvorili vnútornú funkciu add_tax_to_result(), v ktorej sme k pôvodnej cene objednávky pridali aj daň. K pozičným argumentom *args sme pristúpili cez ich index v hranatých zátvorkách (args[0] v našom prípade pre price_per_item). To nám umožnilo vziať iba dva parametre (price_per_item a quantity) a z nich vypočítať 21% daň, ktorú sme následne pripočítali k celkovej cene.

Pre prístup ku kľúčovým argumentom uloženým v **kwargs by sme do hranatých zátvoriek uviedli názov kľúča, napríklad kwargs["discount"], ak by sme chceli získať hodnotu parametra discount.

Návratovými hodnotami *args a **kwargs sú tuple a slovník. Tieto dátové typy ešte nepoznáme, zoznámime sa s nimi až v kurze Kolekcie v Pythone. Obe návratové hodnoty si ale kedykoľvek ľahko prevedieme na zoznam. Zoznam pozičných argumentov nám vráti volanie list(args) a zoznam dvojíc kľúčových argumentov (názov argumentu, hodnota) volania list(kwargs.items()).

Viacnásobné dekorovanie

Jednej funkcii môžeme priradiť viac dekorátorov. Hovoríme potom o viacnásobnom dekorovaní. Dekorátory sa aplikujú vo vrstvách, pričom najprv sa aplikuje dekorátor, ktorý je najbližšie k funkcii. Nakoniec sa aplikuje ten, ktorý je v kóde najvyššie. Na zjednodušenie si môžeme predstaviť každý dekorátor ako obal, do ktorého postupne balíme našu funkciu.

Na ukážku pridáme k predchádzajúcemu príkladu ešte náš dekorátor s časovou pečiatkou, ktorý upravíme, aby bol univerzálnejší:

from datetime import datetime

def add_tax(decorated_function):
    def add_tax_to_result(*args, **kwargs):
        price = decorated_function(*args, **kwargs)
        price_with_tax = price + (args[0] * args[1]) * 0.21
        return f"Total order price with tax is: {price_with_tax:.2f} EUR"
    return add_tax_to_result

def add_timestamp(decorated_function):
    def modify_message(*args, **kwargs):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return "[" + timestamp + "] " + str(decorated_function(*args, **kwargs))
    return modify_message

@add_timestamp
@add_tax
def record_order(price_per_item, quantity, shipping=0, discount=0):
    return price_per_item * quantity + shipping - discount

print(record_order(200, 3, shipping=50, discount=100))

V prípade, že zavoláme funkciu record_order(), Python najskôr obalí pôvodnú funkciu dekorátorom add_tax(). Ten vráti novú funkciu, ktorá po zavolaní vráti reťazec obsahujúci cenu s pripočítanou daňou. Potom je táto nová funkcia obalená dekorátorom add_timestamp(), ktorý upravuje jej správanie tak, že pred reťazec s cenou a daňou pridá aktuálny čas vo forme časovej pečiatky.

V nasledujúcej lekcii, Dekorátory druhýkrát - Parametrické a triedne dekorátory, budeme v téme dekorátorov pokračovať.


 

Predchádzajúci článok
Riešené úlohy k 14.-15. lekcii OOP v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Dekorátory druhýkrát - Parametrické a triedne dekorátory
Článok pre vás napísal Karel Zaoral
Avatar
Užívateľské hodnotenie:
110 hlasov
Karel Zaoral
Aktivity