Les décorateurs sont une partie importante de Python. En termes simples : ce sont des fonctions qui modifient les fonctionnalités d'autres fonctions. Ils aident à rendre notre code plus court et plus Pythonique. La plupart des débutants ne savent pas où les utiliser, alors je vais partager quelques domaines où les décorateurs peuvent rendre votre code plus propre. D'abord, discutons de la façon d'écrire vos propres décorateurs.

C'est probablement l'un des concepts les plus difficiles à maîtriser. Nous allons en discuter une étape à la fois afin que vous puissiez bien le comprendre.

Tout est un objet

D'abord, comprenons les fonctions en Python :

def hi(name="yasoob"): return "hi " + name print(hi()) # sortie : 'hi yasoob' # Nous pouvons même assigner une fonction à une variable, par exemple greet = hi # Nous n'utilisons pas de parenthèses ici, car nous n'appelons pas la fonction hi # mais plutôt la plaçons dans la variable greet. Essayons d'exécuter ceci. print(greet()) # sortie : 'hi yasoob' # Si nous supprimons l'ancienne fonction hi, voyons ce qui se passe ! del hi print(hi()) # sortie : NameError print(greet()) #sorties : 'hi yasoob'

Définir des fonctions à l'intérieur de fonctions

C'étaient les bases des fonctions. Poussons vos connaissances un peu plus loin. En Python, nous pouvons définir une fonction à l'intérieur d'une autre fonction :

def hi(name="yasoob"): print("vous êtes maintenant à l'intérieur de la fonction hi()") def greet(): return "vous êtes maintenant dans la fonction greet()" def welcome(): return "vous êtes maintenant dans la fonction welcome()" print(greet()) print(welcome()) print("vous êtes maintenant de retour dans la fonction hi()") hi() #sortie : vous êtes maintenant à l'intérieur de la fonction hi() # vous êtes maintenant dans la fonction greet() # vous êtes maintenant dans la fonction welcome() # vous êtes maintenant de retour dans la fonction hi() # Ce qui précède montre que chaque fois que vous appelez hi(), greet() et welcome() seront appelées en même temps. # Ensuite, les fonctions greet() et welcome() ne sont pas accessibles en dehors de la fonction hi(), par exemple : greet() #sorties : NameError: name 'greet' is not defined

Maintenant, nous savons que nous pouvons définir d'autres fonctions à l'intérieur d'une fonction. C'est-à-dire : nous pouvons créer des fonctions imbriquées. Maintenant, vous devez apprendre un peu plus : les fonctions peuvent aussi renvoyer des fonctions.

Retourner des fonctions à partir de fonctions

En fait, il n'est pas nécessaire d'exécuter une autre fonction à l'intérieur d'une fonction ; nous pouvons aussi la retourner comme sortie :

def hi(name="yasoob"): def greet(): return "vous êtes maintenant dans la fonction greet()" def welcome(): return "vous êtes maintenant dans la fonction welcome()" if name == "yasoob": return greet else: return welcome a = hi() print(a) #sorties : <function greet at 0x7f2143c01500> # Ce qui précède montre clairement que `a` pointe maintenant vers la fonction greet() à l'intérieur de la fonction hi() # Maintenant essayez ceci print(a()) #sorties : vous êtes maintenant dans la fonction greet()

Regardez à nouveau ce code. Dans l'instruction if/else, nous retournons greet et welcome, pas greet() et welcome(). Pourquoi cela ? C'est parce que lorsque vous mettez une paire de parenthèses après, la fonction est exécutée ; cependant, si vous ne mettez pas de parenthèses après, elle peut être transmise et assignée à d'autres variables sans être exécutée. Comprenez-vous ? Laissez-moi expliquer un peu plus en détail.

  • Quand nous écrivonsa = hi(),hi()elle est exécutée, et comme le paramètre name a pour valeur par défaut yasoob, la fonctiongreet()est retournée.

  • Nous pouvons aussi afficherhi()(), ce qui donnera en sortie :vous êtes maintenant dans la fonction greet()。

  • Si nous changeons l'instruction ena = hi(name = "ali"), alorswelcome()la fonction sera retournée.

Passer une fonction comme argument à une autre fonction

def hi(): return "hi yasoob!" def doSomethingBeforeHi(func): print("Je fais un travail ennuyeux avant d'exécuter hi()") print(func()) doSomethingBeforeHi(hi) #sorties : Je fais un travail ennuyeux avant d'exécuter hi() # salut yasoob !

Vous avez maintenant toutes les connaissances nécessaires pour apprendre ce que sont réellement les décorateurs. Les décorateurs vous permettent d'exécuter du code avant et après une fonction.

Votre premier décorateur

Dans l'exemple précédent, nous avons en fait déjà créé un décorateur ! Maintenant, modifions le décorateur précédent et écrivons un programme légèrement plus utile :

def a_new_decorator(a_func): def wrapTheFunction(): print("Je fais un travail ennuyeux avant d'exécuter a_func()") a_func() print("Je fais un travail ennuyeux après avoir exécuté a_func()") return wrapTheFunction def a_function_requiring_decoration(): print("Je suis la fonction qui a besoin d'une décoration pour éliminer ma mauvaise odeur") a_function_requiring_decoration() #sorties : "Je suis la fonction qui a besoin d'une décoration pour éliminer ma mauvaise odeur" a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) #maintenant a_function_requiring_decoration est enveloppée par wrapTheFunction() a_function_requiring_decoration() #sorties : Je fais un travail ennuyeux avant d'exécuter a_func() # Je suis la fonction qui a besoin d'une décoration pour éliminer ma mauvaise odeur # Je fais un travail ennuyeux après avoir exécuté a_func()

Vous voyez ? Nous venons juste d'appliquer les principes que nous avons appris plus tôt. C'est exactement ce que font les décorateurs en Python ! Ils enveloppent une fonction et modifient son comportement d'une manière ou d'une autre. Maintenant, vous vous demandez peut-être : dans notre code, nous n'avons pas utilisé le@symbole ? Ce n'est qu'un raccourci pour générer une fonction décorée. Voici comment nous utilisons@pour exécuter le code précédent :

@a_new_decorator def a_function_requiring_decoration(): """Hé, toi ! Décore-moi !""" print("Je suis la fonction qui a besoin d'une décoration pour " "éliminer ma mauvaise odeur") a_function_requiring_decoration() #sorties : Je fais un travail ennuyeux avant d'exécuter a_func() # Je suis la fonction qui a besoin d'une décoration pour éliminer ma mauvaise odeur # Je fais un travail ennuyeux après avoir exécuté a_func() #le @a_new_decorator n'est qu'une manière abrégée de dire : a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

J'espère que vous avez maintenant une compréhension de base du fonctionnement des décorateurs Python. Il y a un problème si nous exécutons le code suivant :

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

Ce n'est pas ce que nous voulions ! La sortie devrait être "a_function_requiring_decoration". Ici, la fonction a été remplacée par warpTheFunction. Cela a écrasé le nom et la docstring de notre fonction. Heureusement, Python nous fournit une fonction simple pour résoudre ce problème, à savoir functools.wraps. Modifions l'exemple précédent pour utiliser functools.wraps :

from functools import wraps def a_new_decorator(a_func): @wraps(a_func) def wrapTheFunction(): print("Je fais un travail ennuyeux avant d'exécuter a_func()") a_func() print("Je fais un travail ennuyeux après avoir exécuté a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hé yo ! Décore-moi !""" print("Je suis la fonction qui a besoin d'un peu de décoration pour " "enlever ma mauvaise odeur") print(a_function_requiring_decoration.__name__) # Sortie : a_function_requiring_decoration

C'est beaucoup mieux maintenant. Ensuite, apprenons quelques cas d'utilisation courants des décorateurs.

Spécification du modèle :

from functools import wraps def decorator_name(f): @wraps(f) def decorated(*args, **kwargs): if not can_run: return "La fonction ne s'exécutera pas" return f(*args, **kwargs) return decorated @decorator_name def func(): return("La fonction est en cours d'exécution") can_run = True print(func()) # Sortie : La fonction est en cours d'exécution can_run = False print(func()) # Sortie : La fonction ne s'exécutera pas

Remarque :@wrapsIl accepte une fonction à décorer et ajoute la fonctionnalité de copier le nom de la fonction, la docstring, la liste des arguments, etc. Cela nous permet d'accéder aux attributs de la fonction avant décoration à l'intérieur du décorateur.


Cas d'utilisation

Voyons maintenant où les décorateurs brillent vraiment, et comment leur utilisation facilite la gestion de certaines choses.

Authorization

Les décorateurs peuvent aider à vérifier si quelqu'un est autorisé à utiliser un endpoint d'une application web. Ils sont très utilisés dans les frameworks web Flask et Django. Voici un exemple d'utilisation de l'autorisation basée sur des décorateurs :

from functools import wraps def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated

Journalisation

La journalisation est un autre point fort de l'utilisation des décorateurs. Voici un exemple :

from functools import wraps def logit(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " a été appelée") return func(*args, **kwargs) return with_logging @logit def addition_func(x): """Faire un peu de calcul.""" return x + x result = addition_func(4) # Sortie : addition_func a été appelée

Je suis sûr que vous pensez déjà à une autre utilisation astucieuse des décorateurs.


Décorateurs avec paramètres

Réfléchissez à ceci : @wraps n'est-il pas aussi un décorateur ? Mais il prend un paramètre, tout comme n'importe quelle fonction ordinaire peut le faire. Alors pourquoi ne faisons-nous pas cela aussi ? C'est parce que lorsque vous utilisez la syntaxe @my_decorator, vous appliquez une fonction d'encapsulation qui prend une seule fonction comme argument. Rappelez-vous, tout en Python est un objet, et cela inclut les fonctions ! Avec cela à l'esprit, nous pouvons écrire une fonction qui renvoie une fonction d'encapsulation.

Intégrer des décorateurs dans des fonctions

Revenons à l'exemple de journalisation et créons une fonction d'encapsulation qui nous permet de spécifier un fichier journal pour la sortie.

from functools import wraps def logit(logfile='out.log'): def logging_decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " a été appelée" print(log_string) # Ouvrir le fichier journal et écrire le contenu with open(logfile, 'a') as opened_file: # Journaliser maintenant dans le fichier journal spécifié opened_file.write(log_string + '\n') return func(*args, **kwargs) return wrapped_function return logging_decorator @logit() def myfunc1(): pass myfunc1() # Sortie : myfunc1 a été appelée # Maintenant un fichier appelé out.log apparaît, et son contenu est la chaîne ci-dessus @logit(logfile='func2.log') def myfunc2(): pass myfunc2() # Sortie : myfunc2 a été appelée # Maintenant un fichier appelé func2.log apparaît, et son contenu est la chaîne ci-dessus

Classes de décorateurs

Maintenant, nous avons un décorateur logit adapté à la production, mais lorsque certaines parties de notre application sont encore fragiles, les exceptions peuvent nécessiter une attention plus urgente. Par exemple, parfois vous voulez simplement enregistrer dans un fichier. D'autres fois, vous voulez envoyer un problème qui attire votre attention par e-mail tout en conservant un journal pour archivage. C'est un scénario d'utilisation de l'héritage, mais jusqu'à présent nous n'avons vu que des fonctions utilisées pour construire des décorateurs.

Heureusement, les classes peuvent aussi être utilisées pour construire des décorateurs. Alors maintenant, reconstruisons logit en utilisant une classe plutôt qu'une fonction.

from functools import wraps class logit(object): def __init__(self, logfile='out.log'): self.logfile = logfile def __call__(self, func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " a été appelé" print(log_string) # Ouvrir le fichier journal et écrire with open(self.logfile, 'a') as opened_file: # Maintenant, journaliser dans le fichier spécifié opened_file.write(log_string + '\n') # Maintenant, envoyer une notification self.notify() return func(*args, **kwargs) return wrapped_function def notify(self): # logit ne fait que journaliser, ne fait rien d'autre pass

Cette implémentation présente l'avantage supplémentaire d'être plus propre que l'approche par fonction imbriquée, et l'encapsulation d'une fonction utilise toujours la même syntaxe qu'auparavant :

@logit()
def myfunc1():
    pass

Maintenant, créons une sous-classe de logit pour ajouter une fonctionnalité d'e-mail (bien que le sujet de l'e-mail ne soit pas développé ici).

class email_logit(logit): '''Une version d'implémentation de logit qui peut envoyer un e-mail à l'administrateur lorsque la fonction est appelée''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_logit, self).__init__(*args, **kwargs) def notify(self): # Envoyer un e-mail à self.email # Nous n'implémenterons pas cela ici pass

Désormais, @email_logit aura le même effet que @logit, mais en plus de la journalisation, il enverra également un e-mail à l'administrateur.

Adresse de l'article original : https://eastlakeside.gitbooks.io/interpy-zh/content/decorators/