Accéder au contenu principal

Tutoriel sur les décorateurs Python

Dans ce tutoriel, apprenez à mettre en œuvre des décorateurs en Python.
Actualisé 4 déc. 2024  · 11 min de lecture

Un décorateur est un modèle de conception en Python qui permet à un utilisateur d'ajouter de nouvelles fonctionnalités à un objet existant sans en modifier la structure. Les décorateurs sont généralement appliqués aux fonctions et jouent un rôle crucial dans l'amélioration ou la modification du comportement des fonctions. Traditionnellement, les décorateurs sont placés avant la définition de la fonction que vous souhaitez décorer. Dans ce tutoriel, nous allons montrer comment utiliser efficacement les décorateurs dans les fonctions Python.

En Python, les fonctions sont des citoyens de première classe. Cela signifie qu'ils supportent des opérations telles que la transmission en tant qu'argument, le retour d'une fonction, la modification et l'affectation à une variable. Cette propriété est cruciale car elle permet de traiter les fonctions comme n'importe quel autre objet dans Python, ce qui permet une plus grande flexibilité dans la programmation.

Pour exécuter facilement vous-même tous les exemples de code de ce tutoriel, vous pouvez créer gratuitement un classeur DataLab dans lequel Python est préinstallé et qui contient tous les exemples de code. Pour plus de pratique sur les décorateurs, consultez cet exercice pratique de DataCamp.

Apprenez Python à partir de zéro

Maîtrisez Python pour la science des données et acquérez des compétences recherchées.
Commencez à apprendre gratuitement

Affectation de fonctions à des variables

Pour commencer, nous créons une fonction qui ajoutera un à un nombre chaque fois qu'elle sera appelée. Nous assignerons ensuite la fonction à une variable et utiliserons cette variable pour appeler la fonction.

def plus_one(number):
    return number + 1

add_one = plus_one
add_one(5)
6

Définition de fonctions à l'intérieur d'autres fonctions 

Ensuite, nous allons illustrer comment vous pouvez définir une fonction à l'intérieur d'une autre fonction en Python. Restez avec moi, nous allons bientôt découvrir en quoi tout cela est pertinent pour créer et comprendre les décorateurs en Python.

def plus_one(number):
    def add_one(number):
        return number + 1


    result = add_one(number)
    return result
plus_one(4)
5

Passer des fonctions comme arguments à d'autres fonctions

Les fonctions peuvent également être transmises en tant que paramètres à d'autres fonctions. Nous allons l'illustrer ci-dessous.

def plus_one(number):
    return number + 1

def function_call(function):
    number_to_add = 5
    return function(number_to_add)

function_call(plus_one)
6

Fonctions renvoyant à d'autres fonctions

Une fonction peut également générer une autre fonction. Nous allons le montrer ci-dessous à l'aide d'un exemple.

def hello_function():
    def say_hi():
        return "Hi"
    return say_hi
hello = hello_function()
hello()
'Hi'

Comprendre les fermetures

Python permet à une fonction imbriquée d'accéder à la portée extérieure de la fonction englobante. Il s'agit d'un concept essentiel dans les décorateurs, connu sous le nom de "fermeture".

Une fermeture en Python est une fonction qui se souvient de l'environnement dans lequel elle a été créée, même après que cet environnement n'est plus actif. Cela signifie qu'une fonction imbriquée peut "refermer" les variables de la portée qui l'entoure et continuer à les utiliser.

Les fermetures sont essentielles pour comprendre les décorateurs, car ceux-ci reposent sur la capacité d'une fonction enveloppante imbriquée à accéder à l'état de la fonction décoratrice qui l'entoure et à le modifier.

Exemple de fermeture :

def outer_function(message):
    def inner_function():
        print(f"Message from closure: {message}")
    return inner_function

closure_function = outer_function("Hello, closures!")
closure_function()
# Output: Message from closure: Hello, closures!

Dans cet exemple :

  • inner_function est une fermeture car elle accède à message, une variable de la portée qui l'entoure (outer_function).
  • Même si l'exécution de outer_function est terminée, inner_function conserve l'accès à message.

Lorsque vous créez un décorateur, la fonction enveloppante (à l'intérieur du décorateur) est une fermeture. Il conserve l'accès à la fonction décorée et à tout état ou argument supplémentaire défini dans la fonction décoratrice. Par exemple :

def simple_decorator(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

@simple_decorator
def greet():
    print("Hello!")

greet()
# Output:
# Before the function call
# Hello!
# After the function call

Ici, wrapper est une fermeture qui se souvient de la fonction greet et ajoute un comportement avant et après son exécution.

Création de décorateurs

Ces conditions préalables étant posées, créons un décorateur simple qui convertira une phrase en majuscules. Pour ce faire, nous définissons un "wrapper" à l'intérieur d'une fonction fermée. Comme vous pouvez le constater, cette fonction est très similaire à la fonction à l'intérieur d'une autre fonction que nous avons créée plus tôt.

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase

    return wrapper

Notre fonction décoratrice prend une fonction en argument, et nous allons donc définir une fonction et la passer à notre décorateur. Nous avons appris précédemment que nous pouvions assigner une fonction à une variable. Nous utiliserons cette astuce pour appeler notre fonction de décorateur.

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'

Cependant, Python nous offre un moyen beaucoup plus simple d'appliquer des décorateurs. Il suffit d'utiliser le symbole @ devant la fonction que l'on souhaite décorer. Nous allons le montrer en pratique ci-dessous.

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()
'HELLO THERE'

Appliquer plusieurs décorateurs à une seule fonction

Nous pouvons utiliser plusieurs décorateurs pour une seule fonction. Cependant, les décorateurs seront appliqués dans l'ordre dans lequel nous les avons appelés. Nous allons définir ci-dessous un autre décorateur qui divise la phrase en une liste. Nous allons ensuite appliquer les décorateurs uppercase_decorator et split_string à une seule fonction.

import functools
def split_string(function):
    @functools.wraps(function)
    def wrapper():
        func = function()
        splitted_string = func.split()
        return splitted_string

    return wrapper 
@split_string
@uppercase_decorator
def say_hi():
    return 'hello there'
say_hi()
['HELLO', 'THERE']

Le résultat ci-dessus montre que l'application des décorateurs se fait de bas en haut. Si nous avions interverti l'ordre, nous aurions constaté une erreur, car les listes n'ont pas d'attribut upper. La phrase a d'abord été convertie en majuscules, puis divisée en liste.

Note: Lorsque vous empilez des décorateurs, il est courant d'utiliser functools.wraps pour garantir que les métadonnées de la fonction d'origine sont préservées tout au long du processus d'empilement. Cela permet de maintenir la clarté et la cohérence dans le débogage et la compréhension des propriétés de la fonction décorée.

Acceptation d'arguments dans les fonctions du décorateur

Il peut arriver que nous ayons besoin de définir un décorateur qui accepte des arguments. Pour ce faire, nous transmettons les arguments à la fonction "wrapper". Les arguments seront ensuite transmis à la fonction décorée au moment de l'appel.

def decorator_with_arguments(function):
    def wrapper_accepting_arguments(arg1, arg2):
        print("My arguments are: {0}, {1}".format(arg1,arg2))
        function(arg1, arg2)
    return wrapper_accepting_arguments


@decorator_with_arguments
def cities(city_one, city_two):
    print("Cities I love are {0} and {1}".format(city_one, city_two))

cities("Nairobi", "Accra")

My arguments are: Nairobi, Accra Cities I love are Nairobi and Accra

Note : Il est essentiel de veiller à ce que le nombre d'arguments du décorateur (arg1, arg2 dans cet exemple) corresponde au nombre d'arguments de la fonction enveloppée (cities dans cet exemple). Cet alignement est essentiel pour éviter les erreurs et garantir un fonctionnement correct lors de l'utilisation de décorateurs avec des arguments.

Définition des décorateurs d'usage général

Pour définir un décorateur général pouvant être appliqué à n'importe quelle fonction, nous utilisons args et **kwargs. args et **kwargs collectent tous les arguments de position et de mot-clé et les stockent dans les variables args et kwargs. args et kwargs nous permettent de passer autant d'arguments que nous le souhaitons lors de l'appel d'une fonction.

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
        print('The positional arguments are', args)
        print('The keyword arguments are', kwargs)
        function_to_decorate(*args)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("No arguments here.")

function_with_no_argument()
The positional arguments are ()
The keyword arguments are {}
No arguments here.

Voyons comment nous pouvons utiliser le décorateur en utilisant des arguments positionnels.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
The positional arguments are (1, 2, 3)
The keyword arguments are {}
1 2 3

Les arguments de type mot-clé sont transmis à l'aide de mots-clés. Vous trouverez ci-dessous une illustration de ce phénomène.

@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
    print("This has shown keyword arguments")

function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
The positional arguments are ()
The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments

Note : L'utilisation de **kwargs dans le décorateur lui permet de traiter les arguments de type mot-clé. Cela rend le décorateur polyvalent et lui permet de gérer une grande variété de types d'arguments lors des appels de fonctions.

Passer des arguments au décorateur

Voyons maintenant comment passer des arguments au décorateur lui-même. Pour ce faire, nous définissons un créateur de décorateur qui accepte des arguments, puis nous définissons un décorateur à l'intérieur de celui-ci. Nous définissons ensuite une fonction enveloppante à l'intérieur du décorateur, comme nous l'avons fait précédemment.

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
    def decorator(func):
        def wrapper(function_arg1, function_arg2, function_arg3) :
            "This is the wrapper function"
            print("The wrapper can access all the variables\n"
                  "\t- from the decorator maker: {0} {1} {2}\n"
                  "\t- from the function call: {3} {4} {5}\n"
                  "and pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,decorator_arg3,
                          function_arg1, function_arg2,function_arg3))
            return func(function_arg1, function_arg2,function_arg3)

        return wrapper

    return decorator

pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
    print("This is the decorated function and it only knows about its arguments: {0}"
           " {1}" " {2}".format(function_arg1, function_arg2,function_arg3))

decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
    - from the decorator maker: Pandas Numpy Scikit-learn
    - from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools

Débogage des décorateurs

Comme nous l'avons remarqué, les décorateurs enveloppent les fonctions. Le nom de la fonction d'origine, sa docstring et la liste des paramètres sont tous masqués par la fermeture de l'enveloppe : Par exemple, lorsque nous essayons d'accéder aux métadonnées de decorated_function_with_arguments, nous voyons les métadonnées de la fermeture du wrapper. Cela pose un problème lors du débogage.

decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'

Pour résoudre ce problème, Python propose un décorateur functools.wraps. Ce décorateur copie les métadonnées perdues de la fonction non décorée vers la fermeture décorée. Voyons comment procéder.

import functools

def uppercase_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper
@uppercase_decorator
def say_hi():
    "This will say hi"
    return 'hello there'

say_hi()
'HELLO THERE'

Lorsque nous vérifions les métadonnées de say_hi, nous remarquons qu'elles font désormais référence aux métadonnées de la fonction et non à celles du wrapper.

say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'

Il est conseillé et de bonne pratique de toujours utiliser functools.wraps pour définir les décorateurs. Cela vous évitera bien des maux de tête lors du débogage.

Décorateurs basés sur les classes

Si les décorateurs basés sur des fonctions sont courants, Python vous permet également de créer des décorateurs basés sur des classes, qui offrent une plus grande flexibilité et une meilleure maintenabilité, en particulier pour les cas d'utilisation complexes. Un décorateur basé sur une classe est une classe dotée d'une méthode __call__ qui lui permet de se comporter comme une fonction.

class UppercaseDecorator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        result = self.function(*args, **kwargs)
        return result.upper()

@UppercaseDecorator
def greet():
    return "hello there"

print(greet())
# Output: HELLO THERE

Comment cela fonctionne-t-il ?

  1. La méthode __init__ initialise le décorateur avec la fonction à décorer.
  2. La méthode __call__ est invoquée lorsque la fonction décorée est appelée, ce qui permet au décorateur de modifier son comportement.

Avantages des décorateurs basés sur les classes :

  • Décorateurs d'état: Les décorateurs basés sur les classes peuvent maintenir l'état à l'aide de variables d'instance, contrairement aux décorateurs basés sur les fonctions qui nécessitent des fermetures ou des variables globales.
  • Lisibilité: Pour les décorateurs complexes, l'encapsulation de la logique dans une classe peut rendre le code plus organisé et plus facile à comprendre.

Exemple de décorateur avec état :

class CallCounter:
    def __init__(self, function):
        self.function = function
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.function.__name__} has been called {self.count} times.")
        return self.function(*args, **kwargs)

@CallCounter
def say_hello():
    print("Hello!")

say_hello()
say_hello()
# Output:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!

Cas d'utilisation d'un décorateur dans le monde réel : Mise en cache

Le décorateur lru_cache est un outil intégré à Python qui met en cache les résultats des appels de fonctions coûteux. Cela permet d'améliorer les performances en évitant les calculs redondants pour les entrées répétées.

Exemple :

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # Subsequent calls with the same argument are much faster

Autres utilisations courantes des décorateurs :

  • Enregistrement : Cursus des appels de fonction, des arguments et des valeurs de retour à des fins de débogage ou d'audit.

  • Authentification: Renforcez le contrôle d'accès dans les applications web comme Flask ou Django.

  • Délai d'exécution : Mesurez et optimisez le temps d'exécution des fonctions pour les tâches critiques.

  • Mécanisme de réessai : Réessayer automatiquement les appels de fonction qui ont échoué, utile dans les opérations de réseau.

  • Validation des entrées : Validez les arguments de la fonction avant son exécution.

Résumé des décorateurs Python

Les décorateurs modifient dynamiquement la fonctionnalité d'une fonction, d'une méthode ou d'une classe sans avoir à utiliser directement des sous-classes ou à modifier le code source de la fonction décorée. L'utilisation de décorateurs en Python garantit également que votre code est DRY (Don't Repeat Yourself). Les décorateurs peuvent être utilisés dans plusieurs cas, par exemple :

  • Autorisation dans les frameworks Python tels que Flask et Django
  • Enregistrement
  • Mesure du temps d'exécution
  • Synchronisation

Pour en savoir plus sur les décorateurs Python, consultez la bibliothèque des décorateurs de Python.

Devenez développeur Python

Acquérir les compétences de programmation dont tous les développeurs Python ont besoin.

FAQ

Y a-t-il des considérations de performance à prendre en compte lors de l'utilisation de décorateurs ?

Oui, les décorateurs peuvent ajouter de la surcharge parce qu'ils introduisent des appels de fonction supplémentaires. Lorsque les performances sont critiques, il est important de prendre en compte cette surcharge, en particulier si la fonction décorée est appelée fréquemment dans un contexte sensible aux performances.

Les décorateurs peuvent-ils être utilisés avec des méthodes de classe, et si oui, comment ?

Oui, les décorateurs peuvent être appliqués aux méthodes de classe, tout comme les fonctions ordinaires. Le décorateur reçoit la méthode en argument et renvoie une nouvelle méthode ou une version modifiée de la méthode. Cette fonction est généralement utilisée pour la journalisation, le contrôle d'accès ou l'application de conditions préalables.

Comment les décorateurs peuvent-ils être utilisés à des fins de journalisation ?

Les décorateurs peuvent être utilisés pour enregistrer les appels de fonction, leurs arguments et leurs valeurs de retour en enveloppant l'exécution de la fonction avec du code qui enregistre ces détails dans un système d'enregistrement. Cela facilite le traçage et le débogage.

Quelle est la signification du symbole @ dans les décorateurs ?

Le symbole@ est un sucre syntaxique de Python qui simplifie l'application d'un décorateur à une fonction. Il vous permet d'appliquer un décorateur à une fonction directement au-dessus de sa définition, ce qui rend le code plus propre et plus lisible.

Un décorateur peut-il modifier la valeur de retour d'une fonction, et comment cela fonctionne-t-il ?

Oui, un décorateur peut modifier la valeur de retour d'une fonction en modifiant l'instruction de retour dans la fonction enveloppante. Par exemple, il peut transformer le type de données de sortie, le formater ou ajouter un traitement supplémentaire avant de renvoyer le résultat final.

Comment Python gère-t-il la portée des variables lorsqu'une fonction imbriquée accède à une variable à partir de la fonction qui l'entoure ?

Python utilise une règle de portée LEGB (Local, Enclosing, Global, Built-in). Dans le cas des fonctions imbriquées, la fonction imbriquée peut accéder aux variables de la fonction qui l'entoure, ce qui permet des fermetures où la fonction interne conserve l'accès aux variables de la fonction externe même après que cette dernière a fini de s'exécuter.

Sujets

En savoir plus sur Python

Cours

Writing Functions in Python

4 hr
96.5K
Learn to use best practices to write maintainable, reusable, complex functions with good documentation.
Afficher les détailsRight Arrow
Commencer le cours
Voir plusRight Arrow
Apparenté

blog

Architecture de l'entrepôt de données : Tendances, outils et techniques

Apprenez l'essentiel de l'architecture d'un entrepôt de données, des composants clés aux meilleures pratiques, pour construire un système de données évolutif et efficace !
Kurtis Pykes 's photo

Kurtis Pykes

15 min

blog

2022-2023 Rapport annuel DataCamp Classrooms

À l'aube de la nouvelle année scolaire, DataCamp Classrooms est plus motivé que jamais pour démocratiser l'apprentissage des données, avec plus de 7 650 nouveaux Classrooms ajoutés au cours des 12 derniers mois.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

8 min

blog

Q2 2023 DataCamp Donates Digest

DataCamp Donates a offert plus de 20k bourses d'études à nos partenaires à but non lucratif au deuxième trimestre 2023. Découvrez comment des apprenants défavorisés et assidus ont transformé ces opportunités en réussites professionnelles qui ont changé leur vie.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

blog

Célébration de Saghar Hazinyar : Une boursière de DataCamp Donates et une diplômée de Code to Inspire

Découvrez le parcours inspirant de Saghar Hazinyar, diplômée de Code to Inspire, qui a surmonté les défis en Afghanistan et s'est épanouie grâce à une bourse de DataCamp Donates.
Fereshteh Forough's photo

Fereshteh Forough

4 min

blog

Les 20 meilleures questions d'entretien pour les flocons de neige, à tous les niveaux

Vous êtes actuellement à la recherche d'un emploi qui utilise Snowflake ? Préparez-vous à répondre à ces 20 questions d'entretien sur le flocon de neige pour décrocher le poste !
Nisha Arya Ahmed's photo

Nisha Arya Ahmed

15 min

blog

Nous avons fait don de bourses DataCamp Premium à un million de personnes, et ce n'est pas fini.

Réparties entre nos deux programmes d'impact social, DataCamp Classrooms et #DCDonates, les bourses offrent un accès illimité à tout ce que DataCamp Premium a à offrir.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

Voir plusVoir plus