Decoratori Python: come usarlo e perché?

Un decoratore accetta una funzione, aggiunge alcune funzionalità e la restituisce. In questo tutorial imparerai come creare un decoratore e perché dovresti usarlo.

Decoratori in Python

Python ha una caratteristica interessante chiamata decoratori per aggiungere funzionalità a un codice esistente.

Questo è anche chiamato metaprogrammazione perché una parte del programma cerca di modificare un'altra parte del programma in fase di compilazione.

Prerequisiti per l'apprendimento dei decoratori

Per capire i decoratori, dobbiamo prima conoscere alcune cose di base in Python.

Dobbiamo essere a nostro agio con il fatto che tutto in Python (sì, anche le classi), sono oggetti. I nomi che definiamo sono semplicemente identificatori legati a questi oggetti. Le funzioni non fanno eccezione, sono anche oggetti (con attributi). Vari nomi diversi possono essere associati allo stesso oggetto funzione.

Ecco un esempio.

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

Produzione

 Ciao ciao

Quando si esegue il codice, entrambe le funzioni firste secondforniscono lo stesso output. Qui, i nomi firste si secondriferiscono allo stesso oggetto funzione.

Ora le cose iniziano a farsi più strane.

Le funzioni possono essere passate come argomenti a un'altra funzione.

Se si dispone di funzioni usate come map, filtere reducein Python, allora hai già sapere su questo.

Tali funzioni che accettano altre funzioni come argomenti sono anche chiamate funzioni di ordine superiore . Ecco un esempio di tale funzione.

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

Richiamiamo la funzione come segue.

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

Inoltre, una funzione può restituire un'altra funzione.

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

Produzione

 Ciao

Ecco is_returned()una funzione annidata che viene definita e restituita ogni volta che chiamiamo is_called().

Infine, dobbiamo conoscere le chiusure in Python.

Tornando ai decoratori

Le funzioni e i metodi sono chiamati richiamabili in quanto possono essere chiamati.

In effetti, qualsiasi oggetto che implementa il __call__()metodo speciale viene definito richiamabile. Quindi, nel senso più semplice, un decoratore è un chiamabile che restituisce un richiamabile.

Fondamentalmente, un decoratore accetta una funzione, aggiunge alcune funzionalità e la restituisce.

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

Quando esegui i seguenti codici nella shell,

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

Nell'esempio mostrato sopra, make_pretty()è un decoratore. Nella fase di assegnazione:

 pretty = make_pretty(ordinary)

La funzione è ordinary()stata decorata e alla funzione restituita è stato dato il nome pretty.

Possiamo vedere che la funzione decoratore ha aggiunto alcune nuove funzionalità alla funzione originale. È simile a fare un regalo. Il decoratore funge da involucro. La natura dell'oggetto che è stato decorato (vero regalo all'interno) non cambia. Ma ora sembra carino (dato che è stato decorato).

In generale, decoriamo una funzione e la riassegniamo come,

 ordinary = make_pretty(ordinary).

Questo è un costrutto comune e per questo motivo, Python ha una sintassi per semplificarlo.

Possiamo usare il @simbolo insieme al nome della funzione decoratore e posizionarlo sopra la definizione della funzione da decorare. Per esempio,

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

è equivalente a

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

Questo è solo uno zucchero sintattico per implementare decoratori.

Funzioni di decorazione con parametri

Il decoratore di cui sopra era semplice e funzionava solo con funzioni che non avevano parametri. E se avessimo funzioni che hanno parametri come:

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

Questa funzione ha due parametri, a e b. Sappiamo che darà un errore se passiamo in b come 0.

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

Ora facciamo un decoratore per verificare questo caso che causerà l'errore.

 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)

Questa nuova implementazione verrà restituita Nonese si verifica la condizione di errore.

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

In questo modo, possiamo decorare funzioni che accettano parametri.

Un osservatore attento noterà che i parametri della inner()funzione annidata all'interno del decoratore sono gli stessi dei parametri delle funzioni che decora. Tenendo conto di ciò, ora possiamo creare decoratori generali che funzionano con un numero qualsiasi di parametri.

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

Articoli interessanti...