Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

16. diel - Dekorátory v Pythone

V predchádzajúcom cvičení, Riešené úlohy k 13.-14. 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 odokorujeme:

Klikni pre editáciu
  • def write_to_diary(message)
        print(message)
    • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

    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:

    Klikni pre editáciu
    • 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")
      • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

      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ž odokorovaný 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ň:

      Klikni pre editáciu
      • 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))
        • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

        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ší:

        Klikni pre editáciu
        • 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))
          • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

          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 budúcej lekcii, Dekorátory druhýkrát - Parametrické a triedne dekorátory, budeme v téme dekorátorov pokračovať.


           

          Ako sa ti páči článok?
          Pred uložením hodnotenia, popíš prosím autorovi, čo je zleZnakov 0 z 50-500
          Predchádzajúci článok
          Riešené úlohy k 13.-14. 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:
          16 hlasov
          Karel Zaoral
          Aktivity