Az előző bejegyzésben említettük, hogy a closure számos alkalmazási köre közül az egyik a dekorátorok előállítása. Függvénydekorátort úgy kapunk, hogy a closure körülzáró függvényének argumentumaként egy függvényobjektumot adunk át. Ez lesz a dekorálandó függvény. Ebben az esetben a closure általános szerkezetének leírása a következő ábra felső részén látható.

Ebben a lényeg, hogy a dekorálandó függvényt a belső függvényben hívjuk meg és e hívás előtt és után bármilyen utasítássort végrehajthatunk. Maga a dekorálás úgy történik, hogy az ilyen felépítésű körülzáró függvényt meghívjuk a dekorálandó függvénnyel, és a hívás eredményeként kapott függvényobjektumot a dekorálandó függvény nevéhez rendeljük. Ekkor végeredményben a dekorálandó függvény egy olyan módosult változatát kapjuk, amely az eredeti funkcionalitásától valamilyen mértékben eltér, összhatásában annál bővebb lesz. A dekorálás úgy is megvalósítható, hogy a dekorálandó függvény definíciója felett a @ karakter után írjuk a dekorátor nevét. A dekorálás e két módját az ábra alsó része mutatja.
Ez az eljárás hasonlatos ahhoz, mint mikor karácsonykor egy fenyőfát feldíszítünk. A végeredményben felismerhető az eredeti fenyő, de mégis más lett, más élményt ad. Ezért nevezik az így kialakított szerkezetet dekorátornak, mert az eredeti dekorálandó függvényt mintegy feldíszítjük, hogy egy kicsit más, a céljainknak megfelelőbb változatát kapjuk.

A dekorátoroknak sokféle alkalmazási lehetősége van. Ezekből egy az, amikor több függvényből az azonos feladatot ellátó kódrészeket kiszervezzük egy dekorátorba. Ezzel nem csak a kódismétlést csökkentjük, hanem általában átláthatóbb kódot is kapunk. Nézzünk erre egy példát!
Tegyük fel, hogy van két függvényünk, amelyben előfordulhat a nullával való osztás. Ilyen például a reciprok függvény vagy a másodfokú egyenlet gyökeit meghatározó megoldóképlet. A feladat az, hogy adott bemenő számsorozathoz határozzuk meg a függvényértékeket és tároljuk ezeket egy listában.
Mivel nem ismerjük előre a bemenő sorozatot, ezért előfordulhat olyan argumentum, amely nullával osztást eredményez. Viszont nem szeretnénk, hogy emiatt a listaépítés egy kivételdobás okán megszakadjon, ezért úgy döntünk, hogy ilyen esetben None értéket kell kapni és tárolni. Ekkor a listában szembeötlő lesz, hogy hol nem volt értelmezhető a bemeneti értékre a függvény.
A nullával osztás detektálására célszerű egy kivételkezelő szerkezetet alkalmazni, amely a ZeroDivisonError kivétel esetén None visszatérési értéket ad a függvénynek. Megtehetjük, hogy ezt a try…except szerkezetet mindként függvénybe belehelyezzük. Ez azonban kódismétlést jelentene, amit általában célszerű kerülni, továbbá az egyébként egyszerű függvénytörzset áttekinthetőség szempontjából elbonyolítaná. Ezért ezt kiszervezzük egy dekorátorba, majd ezzel dekoráljuk a két függvényt.
Alább látható a dekorátor és a dekorálandó függvények definíciói. A két függvényt eltérő módon dekoráltuk. A kapott eredmények visszaigazolják az elvárt működést.
|
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 28 29 30 31 32 33 34 |
def check_division_by_zero(func): """Ellenőrzi, hogy a func függvény hívásának eredményeképpen történt-e nullával osztás. Ha nem, akkor a hívás eredményével, ha pedig igen, akkor None értékkel tér vissza a dekorált függvény. """ def inner(*args): try: return func(*args) except ZeroDivisionError: return None return inner def reciprocal(x): """Az x reciprokával tér vissza. Ha x nulla ZeroDivisionError kivétel keletkezik.""" return 1 / x reciprocal = check_division_by_zero(reciprocal) # A dekorált reciprocal függvény az x reciprokával tér vissza, ha x nem nulla, egyébként None értékkel.""" @check_division_by_zero def quadratic_roots(a, b, c): """Másodfokú kifejezés gyökeit adja vissza, vagy None-t, ha nullával osztás történt.""" d = b ** 2 - 4 * a * c return (-b + pow(d, 0.5)) / (2 * a), (-b - pow(d, 0.5)) / (2 * a) # TESZT print([reciprocal(z) for z in range(5)]) # Eredmény: [None, 1.0, 0.5, 0.3333333333333333, 0.25] print([quadratic_roots(a, b, c) for a, b, c in [(1, 0, -4), (0, 5, 3), (1, -6, 8)]]) # Eredmény: [(2.0, -2.0), None, (4.0, 2.0)] |
A bemutatott dekorátor a legegyszerűbb szerkezetű. Ebből kiindulva az összetettebb, paraméterezhető dekorátorokkal, valamint a dekorátorok láncolásával részletesen a Python tudásépítés lépésről lépésre című e-könyv „Képességfejlesztés – függvénydekorátorok” című fejezete foglalkozik, amelyben a fontosabb alkalmazásaikra adott példák ismertetése mellett arról is esik szó, hogy mire kell ügyelni a használatukkor.