Mi a függvénydekorátor és mikor használjuk?

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 tryexcept 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.

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.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.