Une introduction détaillée aux décorateurs Python incontournables

高洛峰
Libérer: 2017-03-17 17:31:08
original
1377 Les gens l'ont consulté

Avant de parler des décorateurs Python, je voudrais donner un exemple bien que ce soit un peu sale, il est très pertinent pour le sujet des décorateurs.

La fonction principale des sous-vêtements que chacun possède est de couvrir notre honte, mais en hiver il ne peut pas nous protéger du vent et du froid, que devons-nous faire ? Une façon à laquelle nous avons pensé était de transformer les sous-vêtements pour les rendre plus épais et plus longs. De cette façon, ils ont non seulement pour fonction de couvrir la honte, mais aussi de fournir de la chaleur. Cependant, il y a un problème après avoir transformé les sous-vêtements en pantalons. , , bien qu'il ait toujours une fonction de couverture de honte, il ne s'agit essentiellement plus d'un véritable sous-vêtement. Les gens intelligents ont donc inventé le pantalon et l'ont mis directement sur le sous-vêtement sans affecter le sous-vêtement. Avec le pantalon, le bébé n'aura plus froid. Les décorateurs sont comme les pantalons dont nous parlons ici. Ils apportent de la chaleur à notre corps sans affecter la fonction des sous-vêtements.

Avant de parler de décorateurs, vous devez d'abord comprendre une chose. Les fonctions en Python sont différentes de Java et C. Les fonctions en Python peuvent être transmises à une autre fonction en tant que paramètres comme des variables ordinaires, par exemple :

def foo():
    print("foo")

def bar(func):
    func()

bar(foo)
Copier après la connexion

Retour officiel à notre sujet. Un décorateur est essentiellement une fonction ou une classe Python qui permet à d'autres fonctions ou classes d'ajouter des fonctionnalités supplémentaires sans apporter de modifications au code. La valeur de retour du décorateur est également un objet fonction/classe. Il est souvent utilisé dans des scénarios comportant des exigences transversales, telles que : l'insertion de journaux, les tests de performances, le traitement des transactions, la mise en cache, la vérification des autorisations, etc. Les décorateurs sont une excellente conception pour résoudre de tels problèmes. Avec les décorateurs, nous pouvons extraire une grande quantité de code similaire qui n'a rien à voir avec la fonction elle-même dans les décorateurs et continuer à le réutiliser. En un mot, le but d’un décorateur est d’ajouter des fonctionnalités supplémentaires à un objet existant.

Regardons d'abord un exemple simple, bien que le code réel puisse être beaucoup plus compliqué que cela :

def foo():
    print('i am foo')
Copier après la connexion

Maintenant, il y a une nouvelle exigence, dans l'espoir d'enregistrer le journal d'exécution du fonction, donc dans le code Ajoutez le code de journal dans :

def foo():
    print('i am foo')
    logging.info("foo is running")
Copier après la connexion

Et si les fonctions bar() et bar2() ont des exigences similaires ? Écrire une autre journalisation dans la fonction bar ? Cela entraîne beaucoup de code similaire. Afin de réduire l'écriture répétée de code, nous pouvons le faire et redéfinir une nouvelle fonction : spécifiquement pour traiter le journal, une fois le journal traité, le vrai code métier est exécuté

<🎜. >
def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()

def foo():
    print(&#39;i am foo&#39;)

use_logging(foo)
Copier après la connexion
comme ça Il n'y a logiquement aucun problème, la fonction est implémentée, mais lorsque nous l'appelons, nous n'appelons plus la vraie fonction foo de logique métier, mais la remplaçons par la fonction use_logging, qui détruit la structure de code d'origine. nous devons Au lieu de passer la fonction foo d'origine en tant que paramètre à la fonction use_logging à chaque fois, existe-t-il un meilleur moyen ? Bien sûr, la réponse est celle des décorateurs.

Décorateur simple

def use_logging(func):

def wrapper():
        logging.warn("%s is running" % func.__name__)
return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper

def foo():
    print(&#39;i am foo&#39;)

foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapper
foo()                   # 执行foo()就相当于执行 wrapper()
Copier après la connexion
use_logging est un décorateur, c'est une fonction ordinaire qui enveloppe la fonction func qui y exécute la vraie logique métier. Il semble que foo soit décoré avec use_logging Idem. comme ci-dessus, use_logging renvoie également une fonction, et le nom de cette fonction est wrapper. Dans cet exemple, lorsque la fonction entre et sort, cela s'appelle une section transversale. Cette méthode de programmation est appelée programmation orientée aspect.

@ sucre syntaxique

Si vous êtes en contact avec Python depuis un certain temps, vous devez être familier avec le symbole @ Oui, le symbole @ est du sucre syntaxique pour les décorateurs. dans les fonctions L'endroit où commence la définition, afin que la dernière étape de réaffectation puisse être omise.

def use_logging(func):

def wrapper():
        logging.warn("%s is running" % func.__name__)
return func()
return wrapper

@use_logging
def foo():
    print("i am foo")

foo()
Copier après la connexion
Comme indiqué ci-dessus, avec @ , on peut omettre la phrase foo = use_logging(foo) et appeler directement foo() pour obtenir le résultat souhaité. Avez-vous vu que la fonction foo() n'a pas besoin d'être modifiée de quelque manière que ce soit, ajoutez simplement un décorateur là où elle est définie, et l'appel est toujours le même qu'avant. Si nous avons d'autres fonctions similaires, nous pouvons continuer à appeler. le décorateur pour la décorer sans avoir à modifier la fonction à plusieurs reprises ou à ajouter de nouveaux packages. De cette façon, nous améliorons la réutilisabilité du programme et augmentons la lisibilité du programme.

La raison pour laquelle les décorateurs sont si pratiques à utiliser en Python est que les fonctions Python peuvent être transmises en tant que paramètres à d'autres fonctions comme les objets ordinaires, peuvent être affectées à d'autres variables, peuvent être utilisées comme valeurs de retour et peuvent être défini dans une autre fonction.

*args, **kwargs

Quelqu'un peut se demander : que se passe-t-il si ma fonction de logique métier foo nécessite des paramètres ? Par exemple :

def foo(name):
    print("i am %s" % name)
Copier après la connexion
Nous pouvons spécifier les paramètres lors de la définition de la fonction wrapper :

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
return func(name)
return wrapper
Copier après la connexion
De cette façon, les paramètres définis par la fonction foo peuvent être définis dans la fonction wrapper . À ce moment-là, quelqu'un veut demander à nouveau : et si la fonction foo recevait deux paramètres ? Qu’en est-il des trois paramètres ? De plus, je pourrais en réussir plusieurs. Lorsque le décorateur ne sait pas combien de paramètres foo a, nous pouvons utiliser *args à la place :

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
Copier après la connexion
De cette façon, peu importe le nombre de paramètres définis par foo, je peux les transmettre complètement à func. Cela n'affectera pas la logique métier de foo. À l'heure actuelle, certains lecteurs peuvent se demander : et si la fonction foo définissait également certains paramètres de mots-clés ? Par exemple :

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))
Copier après la connexion
À ce stade, vous pouvez spécifier le mot-clé function pour la fonction wrapper :

def wrapper(*args, **kwargs):
# args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
Copier après la connexion

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
                logging.warn("%s is running" % func.__name__)
elif level == "info":
                logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper

return decorator

@use_logging(level="warn")
def foo(name=&#39;foo&#39;):
    print("i am %s" % name)

foo()
Copier après la connexion

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level=”warn”)调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level=”warn”)等价于@decorator

类装饰器

没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
def __init__(self, func):
        self._func = func

def __call__(self):
print (&#39;class decorator runing&#39;)
        self._func()
print (&#39;class decorator ending&#39;)

@Foo
def bar():
print (&#39;bar&#39;)

bar()
Copier après la connexion

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__      # 输出 &#39;with_logging&#39;
print func.__doc__       # 输出 None
return func(*args, **kwargs)
return with_logging

# 函数
@logged
def f(x):
"""does some math"""
return x + x * x

logged(f)
Copier après la connexion

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
def with_logging(*args, **kwargs):
print func.__name__      # 输出 &#39;f&#39;
print func.__doc__       # 输出 &#39;does some math&#39;
return func(*args, **kwargs)
return with_logging

@logged
def f(x):
"""does some math"""
return x + x * x
Copier après la connexion

装饰器顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass
Copier après la connexion

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)
Copier après la connexion



Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal