Wprowadzenie.
W kolejnym wpisie poświęconym Pythonowi przedstawię w jaki sposób możemy utworzyć własne dekoratory do funkcji. Zalecam, aby przed przystąpieniem do czytania tego wpisu, zapoznać się z poprzednimi wpisami:
- Funkcja jako argument funkcji
- Funkcja o zmiennej liczbie argumentów
- Funkcja w funkcji
- Funkcja zwracająca funkcję
Zakres artykułu.
- Dekoratory funkcji
Dekoratory funkcji
W pierwszym kroku stwórzmy funkcję fun1(), która będzie naszym dekoratorem. Funkcja ta ma przyjmować jeden argument, którym będzie inna funkcja do “udekorowania“. Następnie wewnątrz ciała fun1() stwórzmy funkcję zagnieżdżoną fun2(), w której to napiszmy jednego printa do testowania. Dodatkowo funkcja zagnieżdżona ma zwracać parametr naszego dekoratora, czyli naszą funkcję do udekorowania. Następnie dopisujemy, że nasz dekorator ma zwracać naszą funkcję zagnieżdżoną.
def fun1(fun): def fun2(): print("To jest dekorator") return fun() return fun2
W kolejnym kroku stworzymy funkcję fun3(), która będzie dekorowana przez funkcję fun1(). W tym celu definiujemy funkcję fun3() i dodajemy tylko jednego printa w celu prostszego testowania.
def fun3(): print("Jakaś tam funkcja")
Następnie przejdźmy do programu głównego, gdzie utworzymy zmienną, do której przypiszemy funkcję dekorującą, a jako argument, funkcja dekorująca przyjmie naszą funkcję do udekorowania. Następnie możemy wywołać naszą zmienną jako funkcję, ze względu, że do zmiennej zwróciliśmy funkcję.
xFun = fun1(fun3) xFun()
Cały kod programu wygląda następująco.
def fun1(fun): def fun2(): print("To jest dekorator") return fun() return fun2 def fun3(): print("Jakaś tam funkcja") xFun = fun1(fun3) xFun()
Po wykonaniu programu, w konsoli powinniśmy ujrzeć następujący wynik.
To jest dekorator
Jakaś tam funkcja
Jak widzimy nasza funkcja fun3() została udekorowana funkcją fun1(), jednakże widzimy, że składnia naszego dekorowania jest inna od tej, którą prawdopodobnie znacie. Zanim przejdziemy do kolejnych modyfikacji chciałbym w tym miejscu zaznaczyć, że przykład ten miał na celu pokazanie i wyjaśnienie, jak działa krok po kroku mechanizm dekorowania.
Przejdźmy teraz do zmodyfikowania powyższego przykładu i użyjmy składni do typowego dekorowania funkcji, która zwiększa czytelność kodu.
Funkcję dekorującą fun1() oraz funkcję do udekorowania fun3() pozostawiamy bez zmian, z tym że przed funkcją fun3() napiszmy kod o składni typowego dekoratora czyli @fun1. W ten oto sposób udekorowaliśmy naszą funkcję fun3(). W tym momencie za każdym razem, gdy będziemy wywoływać funkcję fun3(), zostanie ona udekorowana funkcją fun1().
Cały pod programu wygląda następująco.
def fun1(fun): def fun2(): print("To jest dekorator") return fun() return fun2 @fun1 def fun3(): print("Jakaś tam funkcja") fun3()
Po wykonaniu programu, w konsoli powinniśmy ujrzeć następujący wynik, który jest identyczny z poprzednim przykładem.
To jest dekorator
Jakaś tam funkcja
W tym miejscu pewnie zastanawiasz się a co by było, gdyby nasza funkcja do udekorowania przyjmowała argumenty i na dodatek funkcji do udekorowania byłoby kilka z różną liczbą parametrów. W tym miejscu skorzystamy z parametrów przyjmującą zmienną liczbę argumentów, które poznaliśmy w jednym z poprzednich wpisów.
W pierwszej kolejności musimy zmodyfikować funkcję naszego dekoratora w taki sposób, aby funkcja zagnieżdżona przyjmowała zmienną liczbę parametrów pozycyjnych i kluczowych. W tym celu w funkcji zagnieżdżonej dodajemy następujące parametry *args, **kwargs i również umieszczamy je w funkcji, która jest zwracana w funkcji zagnieżdżonej.
def fun1(fun): def fun2(*args, **kwargs): print("To jest dekorator") return fun(*args, **kwargs) return fun2
W kolejnym kroku w pierwszej kolejności z przykładu pierwszego zmodyfikujmy naszą funkcję do udekorowania fun3(), tak aby przyjmowała ona argumenty.
def fun3(x, y, z=7): print("Jakaś tam funkcja") print("x = ", x) print("y = ", y) print("z = ", z)
Następnie do zmiennej przypisujemy nasz dekorator z funkcją do udekorowania jako argument, a następnie naszą zmienną wywołujemy, tak jak to robiliśmy wcześniej z tą różnicą, że nasza zmienna, która jest funkcją będzie przyjmowała nasze argumenty.
xFun = fun1(fun3) xFun(2, "Mój tekst")
Cały kod programu wygląda następująco.
def fun1(fun): def fun2(*args, **kwargs): print("To jest dekorator") return fun(*args, **kwargs) return fun2 def fun3(x, y, z=7): print("Jakaś tam funkcja") print("x = ", x) print("y = ", y) print("z = ", z) xFun = fun1(fun3) xFun(2, "Mój tekst")
Po wykonaniu programu, w konsoli powinniśmy ujrzeć następujący wynik.
To jest dekorator
Jakaś tam funkcja
x = 2
y = Mój tekst
z = 7
Jak widzimy taka konstrukcja dekoratora przenosi bez problemu nasze argumenty do funkcji dekorowanej.
Teraz przejdźmy do przykładu ze składnią z typowym dekoratorem. Funkcja dekoratora pozostaje niezmieniona, tak samo niezmieniona pozostaje nasza funkcja do udekorowania, natomiast tak jak poprzednio przed funkcją do udekorowania dodajemy kod dekorujący naszą funkcję. W tym przypadku wywołanie funkcji udekorowanej jest takie samo jak poprzednio z tym, że dodajemy nasze argumenty.
def fun1(fun): def fun2(*args, **kwargs): print("To jest dekorator") return fun(*args, **kwargs) return fun2 @fun1 def fun3(x, y, z=7): print("Jakaś tam funkcja") print("x = ", x) print("y = ", y) print("z = ", z) fun3(2, "Mój tekst")
Po wykonaniu programu, w konsoli powinniśmy ujrzeć następujący wynik, który jest identyczny z poprzednim przykładem.
To jest dekorator
Jakaś tam funkcja
x = 2
y = Mój tekst
z = 7