Python Decorators: hoe te gebruiken en waarom?

Een decorateur neemt een functie over, voegt wat functionaliteit toe en geeft deze terug. In deze tutorial leer je hoe je een decorateur kunt maken en waarom je deze zou moeten gebruiken.

Decorateurs in Python

Python heeft een interessante functie genaamd decorateurs om functionaliteit toe te voegen aan een bestaande code.

Dit wordt ook wel metaprogrammering genoemd omdat een deel van het programma tijdens het compileren probeert een ander deel van het programma te wijzigen.

Vereisten voor het leren van decorateurs

Om de decorateurs te begrijpen, moeten we eerst een paar basisdingen in Python weten.

We moeten ons op ons gemak voelen met het feit dat alles in Python (ja! Zelfs klassen) objecten zijn. Namen die we definiëren zijn simpelweg identificaties die aan deze objecten zijn gebonden. Functies zijn geen uitzonderingen, het zijn ook objecten (met attributen). Er kunnen verschillende namen aan hetzelfde functieobject worden gekoppeld.

Hier is een voorbeeld.

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

Uitvoer

 Hallo hallo

Wanneer u de code uitvoert, werken beide functies firsten secondgeven ze dezelfde uitvoer. Hier verwijzen de namen firsten secondnaar hetzelfde functieobject.

Nu begint het vreemder te worden.

Functies kunnen als argumenten aan een andere functie worden doorgegeven.

Als je functies zoals map, filteren reducein Python hebt gebruikt , dan weet je dit al.

Dergelijke functies die andere functies als argumenten aannemen, worden ook functies van hogere orde genoemd . Hier is een voorbeeld van een dergelijke functie.

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

We roepen de functie als volgt op.

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

Bovendien kan een functie een andere functie retourneren.

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

Uitvoer

 Hallo

Hier is_returned()is een geneste functie die wordt gedefinieerd en geretourneerd elke keer dat we aanroepen is_called().

Ten slotte moeten we weten over sluitingen in Python.

Terug naar decorateurs

Functies en methoden worden oproepbaar genoemd, zoals ze kunnen worden aangeroepen.

In feite wordt elk object dat de speciale __call__()methode implementeert , opvraagbaar genoemd. Dus in de meest elementaire zin is een decorateur een oproepbare die een oproepbare teruggave retourneert.

Kortom, een decorateur neemt een functie over, voegt functionaliteit toe en geeft deze terug.

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

Wanneer u de volgende codes in de shell uitvoert,

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

In het bovenstaande voorbeeld make_pretty()is een decorateur. In de toewijzingsstap:

 pretty = make_pretty(ordinary)

De functie ordinary()werd versierd en de geretourneerde functie kreeg de naam pretty.

We kunnen zien dat de decorateurfunctie wat nieuwe functionaliteit heeft toegevoegd aan de oorspronkelijke functie. Dit is vergelijkbaar met het inpakken van een cadeau. De decorateur fungeert als een wikkel. De aard van het object dat werd versierd (feitelijk geschenk binnenin) verandert niet. Maar nu ziet het er mooi uit (sinds het is versierd).

Over het algemeen versieren we een functie en wijzen we deze opnieuw toe als,

 ordinary = make_pretty(ordinary).

Dit is een veel voorkomende constructie en daarom heeft Python een syntaxis om dit te vereenvoudigen.

We kunnen het @symbool samen met de naam van de decorateurfunctie gebruiken en boven de definitie van de te decoreren functie plaatsen. Bijvoorbeeld,

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

is gelijk aan

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

Dit is slechts een syntactische suiker om decorateurs te implementeren.

Functies verfraaien met parameters

De bovenstaande decorateur was eenvoudig en werkte alleen met functies die geen parameters hadden. Wat als we functies hadden die parameters bevatten zoals:

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

Deze functie heeft twee parameters, a en b. We weten dat het een foutmelding geeft als we b doorgeven als 0.

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

Laten we nu een decorateur maken om te controleren op dit geval dat de fout veroorzaakt.

 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)

Deze nieuwe implementatie zal terugkeren Noneals de foutconditie zich voordoet.

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

Op deze manier kunnen we functies decoreren die parameters aannemen.

Een scherpe waarnemer zal opmerken dat de parameters van de geneste inner()functie in de decorateur hetzelfde zijn als de parameters van de functies die hij versiert. Hiermee rekening houdend, kunnen we nu algemene decorateurs maken die met een willekeurig aantal parameters werken.

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