Decorateurs Python

Dans cet article, je vais partager avec toi quelques décorateurs Python étonnants qui peuvent réduire ton code de moitié. Cela semble un peu trop irréel ? Eh bien, laisse-moi te montrer tout d’abord comment ils fonctionnent et ensuite pourquoi tu devrais à tout prix les utiliser dans tes projets.

C’est quoi un décorateur Python ?

Les décorateurs Python sont une fonctionnalité puissante qui te permet de modifier le comportement d’une fonction ou d’une classe sans modifier son code source. Il s’agit essentiellement de fonctions qui prennent une autre fonction en argument et renvoient une nouvelle fonction qui enveloppe la fonction d’origine. De cette manière, tu peux ajouter une fonctionnalité ou une logique supplémentaire à la fonction d’origine sans la modifier.

Par exemple, supposons que tu aies une fonction qui imprime un message sur la console :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def hello():
print("Hello world !")
def hello(): print("Hello world !")
def hello():
    print("Hello world !")

Supposons maintenant que tu souhaites mesurer le temps d’exécution de cette fonction. Tu peux écrire une autre fonction qui utilise le module

time
time pour calculer le temps d’exécution et qui appelle ensuite la fonction d’origine :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import time
def measure_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"Durée d'exécution : {end - start} secondes")
return wrapper
import time def measure_time(func): def wrapper(): start = time.time() func() end = time.time() print(f"Durée d'exécution : {end - start} secondes") return wrapper
import time

def measure_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"Durée d'exécution : {end - start} secondes")
    return wrapper

Note que la fonction

measure_time
measure_time renvoie une autre fonction appelée
wrapper
wrapper, qui est la version modifiée de la fonction d’origine. La fonction
wrapper
wrapper fait deux choses : elle enregistre l’heure de début et de fin de l’exécution et appelle la fonction d’origine.

Pour utiliser cette fonction, tu pourrais faire quelque chose comme ceci :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
hello = measure_time(hello)
hello()
hello = measure_time(hello) hello()
hello = measure_time(hello)
hello()

Il en résulterait quelque chose comme ceci :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Hello world !
Durée d'exécution : 0.0010998249053955078 secondes
Hello world ! Durée d'exécution : 0.0010998249053955078 secondes
Hello world !
Durée d'exécution : 0.0010998249053955078 secondes

Comme tu peux le constater, nous avons réussi à ajouter une fonctionnalité supplémentaire à la fonction

hello
hello sans modifier son code. Cependant, il existe une manière plus élégante et plus concise de le faire en utilisant les décorateurs. Les décorateurs sont simplement une petite syntaxe qui te permet d’appliquer une fonction à une autre fonction en utilisant le symbole @. Par exemple, nous pourrions réécrire le code précédent comme suit :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@measure_time
def hello():
print("Hello world !")
hello()
@measure_time def hello(): print("Hello world !") hello()
@measure_time
def hello():
    print("Hello world !")

hello()

Cela produirait le même résultat que précédemment, mais avec beaucoup moins de code. La ligne

@measure_time
@measure_time équivaut à dire
hello = measure_time(hello)
hello = measure_time(hello), mais elle est beaucoup plus propre et plus lisible.

Pourquoi utiliser les décorateurs Python ?

Les décorateurs Python sont utiles pour de nombreuses raisons :

  • Les décorateurs permettent de réutiliser le code et d’éviter les répétitions. Par exemple, si tu as de nombreuses fonctions qui doivent mesurer leur durée d’exécution, tu peux simplement appliquer le même décorateur à chacune d’entre elles au lieu d’écrire le même code encore et encore.
  • Les décorateurs te permettent de séparer les préoccupations et de suivre le principe de la responsabilité unique. Par exemple, si tu as une fonction qui effectue un calcul complexe, tu peux utiliser un décorateur pour gérer le logging, la gestion des erreurs, la mise en cache ou la validation de l’entrée et de la sortie, sans encombrer la logique principale de la fonction.
  • Les décorateurs permettent d’étendre les fonctionnalités de fonctions ou de classes existantes sans modifier leur code source. Par exemple, si tu utilises une bibliothèque tierce qui fournit des fonctions ou des classes utiles, mais que tu souhaites leur ajouter des fonctionnalités ou des comportements supplémentaires, tu peux utiliser des décorateurs pour les envelopper et les adapter à tes besoins.

Quelques exemples de décorateurs Python

Il existe de nombreux décorateurs intégrés en Python, tels que

@staticmethod
@staticmethod,
@classmethod
@classmethod,
@property
@property,
@functools.lru_cache
@functools.lru_cache,
@functools.singledispatch
@functools.singledispatch, etc. Tu peux également créer tes propres décorateurs personnalisés à des fins diverses. Voici quelques exemples de décorateurs Python qui peuvent réduire ton code de moitié :

Le décorateur @timer

Ce décorateur est similaire au décorateur

@measure_time
@measure_time que nous avons vu précédemment, mais il peut être appliqué à n’importe quelle fonction qui prend n’importe quel nombre d’arguments et renvoie n’importe quelle valeur. Il utilise également le décorateur
functools.wraps
functools.wraps pour préserver le nom et la chaîne de caractères de la fonction d’origine. Voici le code :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Durée d'éxecution : {func.__name__}: {end - start} secondes")
return result
return wrapper
import time from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"Durée d'éxecution : {func.__name__}: {end - start} secondes") return result return wrapper
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Durée d'éxecution : {func.__name__}: {end - start} secondes")
        return result
    return wrapper

Tu peux maintenant utiliser ce décorateur pour mesurer la durée d’exécution de n’importe quelle fonction, par exemple :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@timer
def factorial(n):
"""Renvoie la factorielle de n"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
@timer
def fibonacci(n):
"""Renvoie le nième nombre de Fibonacci"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))
@timer def factorial(n): """Renvoie la factorielle de n""" if n == 0 or n == 1: return 1 else: return n * factorial(n - 1) @timer def fibonacci(n): """Renvoie le nième nombre de Fibonacci""" if n == 0 or n == 1: return n else: return fibonacci(n - 1) + fibonacci(n - 2) print(factorial(10)) print(fibonacci(10))
@timer
def factorial(n):
    """Renvoie la factorielle de n"""
    if n == 0 or n == 1:
        return 1
    else:
        return n * factorial(n - 1)

@timer
def fibonacci(n):
    """Renvoie le nième nombre de Fibonacci"""
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

print(factorial(10))
print(fibonacci(10))

Il en résulterait ceci :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Durée d'éxecution : factorial: 9.5367431640625e-07 secondes
Durée d'éxecution : fibonacci: 0.0010149478912353516 secondes
Durée d'éxecution : factorial: 9.5367431640625e-07 secondes Durée d'éxecution : fibonacci: 0.0010149478912353516 secondes
Durée d'éxecution : factorial: 9.5367431640625e-07 secondes
Durée d'éxecution : fibonacci: 0.0010149478912353516 secondes

Le décorateur @debug

Ce décorateur est utile pour le débogage, car il affiche le nom, les arguments et la valeur de retour de la fonction qu’il enveloppe. Il utilise également le décorateur

functools.wraps
functools.wraps pour préserver le nom et la chaîne de caractères de la fonction d’origine. Voici le code :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Appel de {func.__name__} avec les arguments : {args} et les kwargs : {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} renvoie : {result}")
return result
return wrapper
from functools import wraps def debug(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Appel de {func.__name__} avec les arguments : {args} et les kwargs : {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} renvoie : {result}") return result return wrapper
from functools import wraps

def debug(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Appel de {func.__name__} avec les arguments : {args} et les kwargs : {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} renvoie : {result}")
        return result
    return wrapper

Maintenant, tu peux utiliser ce décorateur pour déboguer n’importe quelle fonction, comme par exemple :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@debug
def add(x, y):
"""Renvoie la somme de x et y"""
return x + y
@debug
def greet(name, message="Hello"):
"""Renvoie un message d'accueil avec le nom"""
return f"{message} {name}!"
print(add(2, 3))
print(greet("Alice"))
print(greet("Bob", message="Salut"))
@debug def add(x, y): """Renvoie la somme de x et y""" return x + y @debug def greet(name, message="Hello"): """Renvoie un message d'accueil avec le nom""" return f"{message} {name}!" print(add(2, 3)) print(greet("Alice")) print(greet("Bob", message="Salut"))
@debug
def add(x, y):
    """Renvoie la somme de x et y"""
    return x + y

@debug
def greet(name, message="Hello"):
    """Renvoie un message d'accueil avec le nom"""
    return f"{message} {name}!"

print(add(2, 3))
print(greet("Alice"))
print(greet("Bob", message="Salut"))

Il en résulterait ceci :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Appel de add avec les arguments : (2, 3) et les kwargs : {}
add renvoie : 5
5
Appel de greet avec les arguments : ('Alice',) et les kwargs : {}
greet renvoie : Hello Alice!
Hello Alice!
Appel de greet avec les arguments : ('Bob',) et les kwargs : {'message': 'Salut'}
greet renvoie : Salut Bob!
Salut Bob!
Appel de add avec les arguments : (2, 3) et les kwargs : {} add renvoie : 5 5 Appel de greet avec les arguments : ('Alice',) et les kwargs : {} greet renvoie : Hello Alice! Hello Alice! Appel de greet avec les arguments : ('Bob',) et les kwargs : {'message': 'Salut'} greet renvoie : Salut Bob! Salut Bob!
Appel de add avec les arguments : (2, 3) et les kwargs : {}
add renvoie : 5
5
Appel de greet avec les arguments : ('Alice',) et les kwargs : {}
greet renvoie : Hello Alice!
Hello Alice!
Appel de greet avec les arguments : ('Bob',) et les kwargs : {'message': 'Salut'}
greet renvoie : Salut Bob!
Salut Bob!

Le décorateur @memoize

Ce décorateur est utile pour optimiser les performances des fonctions récursives ou coûteuses, car il met en cache les résultats des appels précédents et les renvoie si les mêmes arguments sont passés à nouveau. Il utilise également le décorateur

functools.wraps
functools.wraps pour préserver le nom et la chaîne de caractères de la fonction d’origine. Voici le code :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
from functools import wraps def memoize(func): cache = {} @wraps(func) def wrapper(*args): if args in cache: return cache[args] else: result = func(*args) cache[args] = result return result return wrapper
from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        else:
            result = func(*args)
            cache[args] = result
            return result
    return wrapper

Maintenant, tu peux utiliser ce décorateur pour mémoriser n’importe quelle fonction, comme par exemple :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@memoize
def factorial(n):
"""Renvoie la factorielle de n"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
@memoize
def fibonacci(n):
"""Renvoie le nième nombre de Fibonacci"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))
@memoize def factorial(n): """Renvoie la factorielle de n""" if n == 0 or n == 1: return 1 else: return n * factorial(n - 1) @memoize def fibonacci(n): """Renvoie le nième nombre de Fibonacci""" if n == 0 or n == 1: return n else: return fibonacci(n - 1) + fibonacci(n - 2) print(factorial(10)) print(fibonacci(10))
@memoize
def factorial(n):
    """Renvoie la factorielle de n"""
    if n == 0 or n == 1:
        return 1
    else:
        return n * factorial(n - 1)
@memoize
def fibonacci(n):
    """Renvoie le nième nombre de Fibonacci"""
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
3628800
55
3628800 55
3628800
55

Le résultat serait le même que précédemment, mais avec un temps d’exécution beaucoup plus rapide, car les résultats sont mis en cache et réutilisés.

Conclusion

Les décorateurs Python sont un moyen puissant et élégant de modifier le comportement des fonctions ou des classes sans changer leur code source. Ils peuvent t’aider à réduire ton code de moitié, à améliorer sa lisibilité, à le réutiliser et à étendre les fonctionnalités du code existant.

J’espère que cet article t’a plu et que tu as appris quelque chose de nouveau.

Publications similaires


0 Commentaires
Le plus récent
Le plus ancien Le plus populaire
Commentaires en ligne
Afficher tous les commentaires