Decorator in Python

Decorator in Python

Questo articolo spiega i decorator in Python.

YouTube Video

Decorator in Python

I decorator in Python sono una funzionalità potente utilizzata per aggiungere funzionalità aggiuntive a funzioni o metodi. I decorator consentono di aggiungere nuove funzionalità senza modificare il codice esistente, migliorando così la riusabilità e la manutenibilità del codice.

Concetti di base sui Decorator

I decorator in Python funzionano prendendo una funzione come argomento e restituendo una nuova funzione con funzionalità aggiuntive. Usando i decorator, è possibile aggiungere facilmente pre-elaborazioni o post-elaborazioni alle funzioni.

Senza i decorator, modificare il comportamento di una funzione richiederebbe di modificare direttamente la funzione stessa. Con i decorator, è possibile estendere le funzionalità senza alterare l'implementazione originale.

Struttura base di un Decorator

 1def my_decorator(func):
 2    def wrapper():
 3        print("Before the function call")
 4        func()
 5        print("After the function call")
 6    return wrapper
 7
 8@my_decorator
 9def say_hello():
10    print("Hello!")
11
12say_hello()

In questo esempio, la funzione my_decorator funge da decorator, avvolgendo la funzione say_hello. Il decorator viene applicato usando la sintassi @my_decorator, e quando si chiama say_hello(), vengono eseguite automaticamente la pre-elaborazione e la post-elaborazione fornite dal decorator.

Come funzionano i Decorator

I decoratori funzionano nei seguenti passaggi.

  1. Il decorator prende una funzione (o un metodo) come argomento.

  2. Definisce una funzione wrapper per eseguire la funzione originale.

  3. La funzione wrapper esegue elaborazioni aggiuntive prima o dopo l'esecuzione della funzione originale.

  4. Il decorator restituisce la funzione wrapper. Di conseguenza, la funzione originale viene sostituita con una nuova funzione decorata.

Decorator per Funzioni con Argomenti

Quando si applica un decorator a una funzione con argomenti, è necessario adattare la funzione wrapper per accettare quegli argomenti.

 1def my_decorator(func):
 2    def wrapper(*args, **kwargs):
 3        print("Before the function call")
 4        result = func(*args, **kwargs)
 5        print("After the function call")
 6        return result
 7    return wrapper
 8
 9@my_decorator
10def greet(name):
11    print(f"Hello, {name}!")
12
13greet("Alice")

Utilizzando *args e **kwargs, è possibile gestire funzioni che accettano qualsiasi numero di argomenti e argomenti con parole chiave. Questo consente di applicare i decorator in modo generico a funzioni con qualsiasi tipo di argomenti.

Esempi di applicazione dei decoratori

Decoratore per il logging

I decoratori sono spesso utilizzati per aggiungere funzionalità di logging. Ad esempio, creando un decoratore che registra prima e dopo l'esecuzione di una funzione, puoi registrare quando la funzione è chiamata e quanto tempo impiega per essere eseguita.

 1import time
 2
 3def log_time(func):
 4    def wrapper(*args, **kwargs):
 5        start_time = time.time()
 6        print(f"Calling function '{func.__name__}'...")
 7        result = func(*args, **kwargs)
 8        end_time = time.time()
 9        print(f"Function '{func.__name__}' completed in {end_time - start_time} seconds")
10        return result
11    return wrapper
12
13@log_time
14def long_task(duration):
15    time.sleep(duration)
16    print("Task completed!")
17
18long_task(2)

In questo esempio, il decoratore log_time misura il tempo di esecuzione di una funzione e lo restituisce come log. La funzione long_task è avvolta con il decoratore e il suo tempo di esecuzione viene registrato durante il runtime.

Decoratore per la gestione dei permessi

I decoratori possono essere utilizzati anche per la gestione dei permessi. Ad esempio, puoi limitare l'elaborazione controllando se un utente ha permessi specifici.

 1def requires_permission(user_role):
 2    def decorator(func):
 3        def wrapper(*args, **kwargs):
 4            if user_role != "admin":
 5                print("Permission denied!")
 6                return
 7            return func(*args, **kwargs)
 8        return wrapper
 9    return decorator
10
11@requires_permission("admin")
12def delete_user(user_id):
13    print(f"User {user_id} has been deleted.")
14
15delete_user(123)  # Executed
16delete_user = requires_permission("guest")(delete_user)
17delete_user(456)  # Permission denied!

Il decoratore requires_permission limita l'esecuzione di una funzione in base al ruolo di un utente. Consolidando tale logica in un decoratore, puoi evitare che la logica di gestione dei permessi sia dispersa in tutto il codice, migliorandone la leggibilità.

Nidificazione dei decoratori

È possibile applicare più decoratori. Quando più decoratori sono applicati a una singola funzione, vengono eseguiti in ordine dall'alto verso il basso.

 1def uppercase(func):
 2    def wrapper(*args, **kwargs):
 3        result = func(*args, **kwargs)
 4        return result.upper()
 5    return wrapper
 6
 7def exclaim(func):
 8    def wrapper(*args, **kwargs):
 9        result = func(*args, **kwargs)
10        return result + "!"
11    return wrapper
12
13@uppercase
14@exclaim
15def greet(name):
16    return f"Hello, {name}"
17
18print(greet("Alice"))  # "HELLO, ALICE!"

In questo esempio, due decoratori sono applicati alla funzione greet. Il decoratore @exclaim aggiunge un punto esclamativo alla fine della stringa, e successivamente @uppercase converte la stringa in maiuscolo.

Decoratori per i metodi delle classi

I decoratori possono essere utilizzati anche con i metodi delle classi. Questo è particolarmente utile quando vuoi controllare il comportamento dei metodi all'interno di una classe. Quando applichi decoratori ai metodi delle classi, devi prestare attenzione agli argomenti come self o cls.

 1def log_method_call(func):
 2    def wrapper(self, *args, **kwargs):
 3        print(f"Calling method '{func.__name__}'...")
 4        return func(self, *args, **kwargs)
 5    return wrapper
 6
 7class MyClass:
 8    @log_method_call
 9    def greet(self, name):
10        print(f"Hello, {name}")
11
12obj = MyClass()
13obj.greet("Bob")

In questo esempio, il decoratore log_method_call è applicato al metodo greet per emettere un log quando il metodo viene chiamato.

Attenzioni con i decoratori

Ci sono alcune considerazioni da tenere a mente quando si usano i decoratori. I decoratori possono potenzialmente cambiare il nome originale della funzione e la stringa di documentazione (ad esempio, __name__ o __doc__), quindi si consiglia di utilizzare functools.wraps per preservare queste informazioni.

1import functools
2
3def my_decorator(func):
4    @functools.wraps(func)
5    def wrapper(*args, **kwargs):
6        print("Before the function call")
7        return func(*args, **kwargs)
8    return wrapper

L'utilizzo di @functools.wraps assicura che i metadati della funzione originale siano correttamente trasmessi alla funzione wrapper.

Riepilogo

I decoratori in Python sono uno strumento molto potente che consente di aggiungere in modo conciso funzionalità aggiuntive a funzioni e metodi. Possono essere utilizzati in vari scenari per ridurre la duplicazione del codice e migliorare la manutenibilità e la riutilizzabilità. Capendo come funzionano i decoratori, puoi scrivere codice più efficiente e flessibile.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video