Python Decorators: Hur man använder det och varför?

En dekoratör tar in en funktion, lägger till lite funktionalitet och returnerar den. I den här handledningen lär du dig hur du kan skapa en dekoratör och varför du ska använda den.

Dekoratörer i Python

Python har en intressant funktion som kallas dekoratörer för att lägga till funktionalitet i en befintlig kod.

Detta kallas också metaprogrammering eftersom en del av programmet försöker ändra en annan del av programmet vid sammanställningstid.

Förutsättningar för att lära sig dekoratörer

För att förstå om dekoratörer måste vi först veta några grundläggande saker i Python.

Vi måste vara bekväma med det faktum att allt i Python (ja! Även klasser) är objekt. Namn som vi definierar är helt enkelt identifierare bundna till dessa objekt. Funktioner är inga undantag, de är också objekt (med attribut). Olika olika namn kan bindas till samma funktionsobjekt.

Här är ett exempel.

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

Produktion

 Hej hej

När du kör koden fungerar båda firstoch secondger samma utdata. Här namnen firstoch secondhänvisar till samma funktionsobjekt.

Nu börjar saker och ting bli konstigare.

Funktioner kan skickas som argument till en annan funktion.

Om du har använt funktioner som map, filteroch reducei Python, då du redan vet om detta.

Sådana funktioner som tar andra funktioner som argument kallas också högre ordningsfunktioner . Här är ett exempel 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 åberopar funktionen enligt följande.

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

Dessutom kan en funktion returnera en annan funktion.

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

Produktion

 Hej

Här is_returned()är en kapslad funktion som definieras och returneras varje gång vi ringer is_called().

Slutligen måste vi veta om stängningar i Python.

Att komma tillbaka till dekoratörer

Funktioner och metoder kallas kallbara som de kan kallas.

I själva verket __call__()kallas varje objekt som implementerar den speciella metoden kallbar. Så i den mest grundläggande betydelsen är en dekoratör en kallbar som returnerar en kallbar.

I grund och botten tar en dekoratör in en funktion, lägger till lite funktionalitet och returnerar den.

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

När du kör följande koder i skal,

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

I exemplet som visas ovan make_pretty()är en dekoratör. I uppdragssteget:

 pretty = make_pretty(ordinary)

Funktionen ordinary()blev dekorerad och den returnerade funktionen fick namnet pretty.

Vi kan se att dekoratörsfunktionen lade till ny funktionalitet i den ursprungliga funktionen. Detta liknar att packa en present. Dekoratören fungerar som ett omslag. Objekten som dekorerades (den faktiska gåvan inuti) förändras inte. Men nu ser det vackert ut (eftersom det blev dekorerat).

Generellt dekorerar vi en funktion och tilldelar den igen som,

 ordinary = make_pretty(ordinary).

Detta är en vanlig konstruktion och av denna anledning har Python en syntax för att förenkla detta.

Vi kan använda @symbolen tillsammans med namnet på dekoratörsfunktionen och placera den ovanför definitionen av funktionen som ska dekoreras. Till exempel,

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

är ekvivalent med

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

Detta är bara ett syntaktiskt socker för att implementera dekoratörer.

Dekorationsfunktioner med parametrar

Ovanstående dekoratör var enkel och det fungerade bara med funktioner som inte hade några parametrar. Vad händer om vi hade funktioner som tog parametrar som:

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

Denna funktion har två parametrar, a och b. Vi vet att det kommer att ge ett fel om vi skickar in b som 0.

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

Låt oss nu skapa en dekoratör för att kontrollera om det här fallet orsakar felet.

 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)

Denna nya implementering kommer att återvända Noneom felförhållandet uppstå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å detta sätt kan vi dekorera funktioner som tar parametrar.

En skarp observatör kommer att märka att parametrarna för den kapslade inner()funktionen inuti dekoratören är desamma som parametrarna för funktioner den dekorerar. Med hänsyn till detta kan vi nu skapa allmänna dekoratörer som fungerar med valfritt antal parametrar.

I Python görs denna magi som function(*args, **kwargs). På detta sätt argskommer att vara tupeln av positionella argument och kwargskommer att vara ordlistan för nyckelordargument. Ett exempel på en sådan dekoratör är:

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

Kedjedekoratörer i Python

Flera dekoratörer kan kedjas i Python.

Det vill säga en funktion kan dekoreras flera gånger med olika (eller samma) dekoratörer. Vi placerar helt enkelt dekoratörerna över önskad funktion.

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

Produktion

 *****************************% %%%%%%%%%%%%%%%%%%%%% %%%%%%%%%% Hej %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ********* *********************

Ovanstående syntax för,

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

är ekvivalent med

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

I vilken ordning vi kedjer dekoratörer. Om vi ​​hade vänt ordern som,

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

Resultatet skulle vara:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************** ********** Hej ****************************** %%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%

Intressanta artiklar...