Python-dekoratører: Hvordan bruges det og hvorfor?

En dekoratør tager en funktion ind, tilføjer nogle funktioner og returnerer den. I denne vejledning lærer du, hvordan du kan oprette en dekoratør, og hvorfor du skal bruge den.

Dekoratører i Python

Python har en interessant funktion kaldet dekoratører for at tilføje funktionalitet til en eksisterende kode.

Dette kaldes også metaprogrammering, fordi en del af programmet forsøger at ændre en anden del af programmet på kompileringstidspunktet.

Forudsætninger for at lære dekoratører

For at forstå om dekoratører skal vi først kende et par grundlæggende ting i Python.

Vi skal være fortrolige med det faktum, at alt i Python (Ja! Selv klasser) er genstande. Navne, som vi definerer, er simpelthen identifikatorer, der er bundet til disse objekter. Funktioner er ingen undtagelser, de er også objekter (med attributter). Forskellige forskellige navne kan bindes til det samme funktionsobjekt.

Her er et eksempel.

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

Produktion

 Hej hej

Når du kører koden, fungerer begge funktioner firstog secondgiver den samme output. Her navne firstog secondrefererer til den samme funktion objekt.

Nu begynder tingene at blive mærkeligere.

Funktioner kan overføres som argumenter til en anden funktion.

Hvis du har brugt funktioner som map, filterog reducei Python, så du allerede kender om dette.

Sådanne funktioner, der tager andre funktioner som argumenter, kaldes også funktioner af højere orden . Her er et eksempel på en sådan funktion.

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

Vi påkalder funktionen som følger.

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

Desuden kan en funktion returnere en anden funktion.

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

Produktion

 Hej

Her is_returned()er en indlejret funktion, der defineres og returneres hver gang vi ringer is_called().

Endelig skal vi vide om lukninger i Python.

Kom tilbage til dekoratører

Funktioner og metoder kaldes kaldbare, som de kan kaldes.

Faktisk __call__()kaldes ethvert objekt, der implementerer den specielle metode, kaldbar. Så i den mest basale forstand er en dekoratør en kaldbar, der returnerer en kaldbar.

Dybest set tager en dekoratør en funktion ind, tilføjer nogle funktioner og returnerer den.

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

Når du kører følgende koder i shell,

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

I eksemplet vist ovenfor make_pretty()er en dekoratør. I opgavetrinnet:

 pretty = make_pretty(ordinary)

Funktionen ordinary()blev dekoreret, og den returnerede funktion fik navnet pretty.

Vi kan se, at dekoratørfunktionen tilføjede nogle nye funktioner til den oprindelige funktion. Dette svarer til at pakke en gave. Dekoratøren fungerer som en indpakning. Arten af ​​det objekt, der blev dekoreret (faktisk gave inde), ændrer sig ikke. Men nu ser det smukt ud (da det blev dekoreret).

Generelt dekorerer vi en funktion og tildeler den igen som,

 ordinary = make_pretty(ordinary).

Dette er en almindelig konstruktion, og af denne grund har Python en syntaks for at forenkle dette.

Vi kan bruge @symbolet sammen med navnet på dekoratørfunktionen og placere det over definitionen af ​​den funktion, der skal dekoreres. For eksempel,

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

svarer til

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

Dette er bare et syntaktisk sukker til implementering af dekoratører.

Dekorationsfunktioner med parametre

Ovenstående dekoratør var enkel, og det fungerede kun med funktioner, der ikke havde nogen parametre. Hvad hvis vi havde funktioner, der tog parametre som:

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

Denne funktion har to parametre, a og b. Vi ved, at det vil give en fejl, hvis vi videregiver b som 0.

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

Lad os nu lave en dekoratør for at kontrollere, om denne sag vil forårsage fejlen.

 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)

Denne nye implementering vender tilbage, Nonehvis fejltilstanden opstår.

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

På denne måde kan vi dekorere funktioner, der tager parametre.

En ivrig observatør vil bemærke, at parametrene for den indlejrede inner()funktion inde i dekoratøren er de samme som parametrene for funktioner, den dekorerer. Når vi tager dette i betragtning, kan vi nu lave generelle dekoratører, der arbejder med et vilkårligt antal parametre.

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 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Interessante artikler...