Dekoratory Pythona: jak go używać i dlaczego?

Dekorator przejmuje funkcję, dodaje funkcjonalność i zwraca ją. W tym samouczku dowiesz się, jak stworzyć dekorator i dlaczego warto go używać.

Dekoratory w Pythonie

Python ma interesującą funkcję zwaną dekoratorami, która dodaje funkcjonalność do istniejącego kodu.

Nazywa się to również metaprogramowaniem, ponieważ część programu próbuje zmodyfikować inną część programu w czasie kompilacji.

Wymagania wstępne do nauki dekoratorów

Aby zrozumieć, czym są dekoratory, musimy najpierw poznać kilka podstawowych rzeczy w Pythonie.

Musimy czuć się komfortowo z tym, że wszystko w Pythonie (tak! Nawet klasy) jest obiektami. Nazwy, które definiujemy, są po prostu identyfikatorami powiązanymi z tymi obiektami. Funkcje nie są wyjątkami, są też obiektami (z atrybutami). Z tym samym obiektem funkcji mogą być powiązane różne nazwy.

Oto przykład.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Wynik

 cześć cześć

Po uruchomieniu kodu obie funkcje firsti seconddają te same dane wyjściowe. Tutaj nazwy firsti secondodnoszą się do tego samego obiektu funkcji.

Teraz sprawy stają się dziwniejsze.

Funkcje mogą być przekazywane jako argumenty do innej funkcji.

Jeśli korzystałeś z funkcji, takich jak map, filteri reducew Pythonie, to już wiesz na ten temat.

Takie funkcje, które przyjmują inne funkcje jako argumenty, nazywane są również funkcjami wyższego rzędu . Oto przykład takiej funkcji.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Wywołujemy funkcję w następujący sposób.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Ponadto funkcja może zwrócić inną funkcję.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Wynik

 cześć

Tutaj is_returned()jest zagnieżdżona funkcja, która jest definiowana i zwracana za każdym razem, gdy wywołujemy is_called().

Na koniec musimy wiedzieć o domknięciach w Pythonie.

Wracając do dekoratorów

Funkcje i metody nazywane są wywoływalnymi, jak można je nazwać.

W rzeczywistości każdy obiekt, który implementuje specjalną __call__()metodę, jest nazywany wywoływalnym. Zatem w najbardziej podstawowym sensie dekorator to obiekt wywoływany, który zwraca wartość wywoływaną.

Zasadniczo dekorator przejmuje funkcję, dodaje jakąś funkcjonalność i zwraca ją.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Po uruchomieniu następujących kodów w powłoce,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

W powyższym przykładzie make_pretty()jest dekoratorem. Na etapie przypisania:

 pretty = make_pretty(ordinary)

Funkcja ordinary()została ozdobiona, a zwracana funkcja otrzymała nazwę pretty.

Widzimy, że funkcja dekoratora dodała nową funkcjonalność do pierwotnej funkcji. Jest to podobne do pakowania prezentu. Dekorator pełni rolę opakowania. Charakter przedmiotu, który został udekorowany (rzeczywisty prezent w środku) nie zmienia się. Ale teraz wygląda ładnie (odkąd został ozdobiony).

Ogólnie rzecz biorąc, dekorujemy funkcję i przypisujemy ją jako,

 ordinary = make_pretty(ordinary).

Jest to typowa konstrukcja iz tego powodu Python ma składnię, która to upraszcza.

Możemy użyć @symbolu wraz z nazwą funkcji dekoratora i umieścić go nad definicją funkcji do zdobienia. Na przykład,

 @make_pretty def ordinary(): print("I am ordinary")

jest równa

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

To tylko cukier syntaktyczny do implementacji dekoratorów.

Dekorowanie funkcji za pomocą parametrów

Powyższy dekorator był prosty i działał tylko z funkcjami, które nie miały żadnych parametrów. A co by było, gdybyśmy mieli funkcje przyjmujące parametry takie jak:

 def divide(a, b): return a/b

Ta funkcja ma dwa parametry, a i b. Wiemy, że wystąpi błąd, jeśli przekażemy b jako 0.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Zróbmy teraz dekorator, który sprawdzi ten przypadek, który spowoduje błąd.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

Ta nowa implementacja powróci, Nonejeśli wystąpi warunek błędu.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

W ten sposób możemy ozdobić funkcje, które przyjmują parametry.

Wnikliwy obserwator zauważy, że parametry inner()funkcji zagnieżdżonej wewnątrz dekoratora są takie same, jak parametry funkcji, które ozdabia. Biorąc to pod uwagę, możemy teraz tworzyć ogólne dekoratory, które działają z dowolną liczbą parametrów.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Interesujące artykuły...