Python Decorators: Hvordan bruke den og hvorfor?

En dekoratør tar inn en funksjon, legger til litt funksjonalitet og returnerer den. I denne opplæringen lærer du hvordan du kan lage en dekoratør og hvorfor du bør bruke den.

Dekoratører i Python

Python har en interessant funksjon kalt dekoratører for å legge til funksjonalitet i en eksisterende kode.

Dette kalles også metaprogrammering fordi en del av programmet prøver å endre en annen del av programmet på kompileringstidspunktet.

Forutsetninger for å lære dekoratører

For å forstå om dekoratører, må vi først vite noen grunnleggende ting i Python.

Vi må være komfortable med det faktum at alt i Python (Ja! Selv klasser) er gjenstander. Navn som vi definerer er ganske enkelt identifikatorer som er bundet til disse objektene. Funksjoner er ingen unntak, de er også objekter (med attributter). Ulike forskjellige navn kan bindes til det samme funksjonsobjektet.

Her er et eksempel.

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

Produksjon

 Hei hei

Når du kjører koden, fungerer begge funksjonene firstog secondgir samme utgang. Her navnene firstog secondrefererer til samme funksjon objekt.

Nå begynner ting å bli merkeligere.

Funksjoner kan overføres som argumenter til en annen funksjon.

Hvis du har brukt funksjoner som map, filterog reducei Python, så du allerede vet om dette.

Slike funksjoner som tar andre funksjoner som argumenter kalles også funksjoner for høyere orden . Her er et eksempel på en slik funksjon.

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

Vi påkaller funksjonen som følger.

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

Videre kan en funksjon returnere en annen funksjon.

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

Produksjon

 Hallo

Her is_returned()er en nestet funksjon som defineres og returneres hver gang vi ringer is_called().

Til slutt må vi vite om Closures in Python.

Komme tilbake til dekoratører

Funksjoner og metoder kalles callable som de kan kalles.

Faktisk __call__()kalles ethvert objekt som implementerer den spesielle metoden. Så i den mest grunnleggende forstand er en dekoratør en kallbar som returnerer en kallbar.

I utgangspunktet tar en dekoratør inn en funksjon, legger til litt funksjonalitet 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 kjører følgende koder i skall,

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

 pretty = make_pretty(ordinary)

Funksjonen ordinary()ble dekorert og den returnerte funksjonen fikk navnet pretty.

Vi kan se at dekoratørfunksjonen la til ny funksjonalitet i den opprinnelige funksjonen. Dette ligner på å pakke en gave. Dekoratøren fungerer som en innpakning. Objektenes art som ble dekorert (faktisk gave inni) endres ikke. Men nå ser det pent ut (siden det ble dekorert).

Generelt dekorerer vi en funksjon og tilordner den på nytt som,

 ordinary = make_pretty(ordinary).

Dette er en vanlig konstruksjon, og av denne grunn har Python en syntaks for å forenkle dette.

Vi kan bruke @symbolet sammen med navnet på dekoratørfunksjonen og plassere det over definisjonen av funksjonen som skal dekoreres. For eksempel,

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

tilsvarer

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

Dette er bare et syntaktisk sukker for å implementere dekoratører.

Dekorasjonsfunksjoner med parametere

Ovenstående dekoratør var enkel, og det fungerte bare med funksjoner som ikke hadde noen parametere. Hva om vi hadde funksjoner som tok inn parametere som:

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

Denne funksjonen har to parametere, a og b. Vi vet at det vil gi en feil hvis vi gir b som 0.

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

La oss nå lage en dekoratør for å se etter denne saken som vil forårsake feilen.

 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 implementeringen vil komme tilbake Nonehvis feiltilstanden oppstå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åten kan vi dekorere funksjoner som tar parametere.

En ivrig observatør vil legge merke til at parametrene til den nestede inner()funksjonen inne i dekoratøren er de samme som parameterne for funksjonene den dekorerer. Når vi tar dette i betraktning, kan vi nå lage generelle dekoratører som fungerer med et hvilket som helst antall parametere.

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