Maison >développement back-end >Tutoriel Python >Pour comprendre les décorateurs Python, il suffit de lire cet article

Pour comprendre les décorateurs Python, il suffit de lire cet article

高洛峰
高洛峰original
2017-02-17 11:07:321650parcourir

Avant de parler des décorateurs Python, je voudrais d'abord donner un exemple, même s'il est 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)

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')

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")

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('i am foo')use_logging(foo)
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 wrapperdef foo():
    print('i am foo')foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapperfoo()                   # 执行foo()就相当于执行 wrapper()
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_loggingdef foo():
    print("i am foo")foo()
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)
Nous pouvons spécifier des 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

这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:

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

如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?比如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

这时,你就可以把 wrapper 函数指定关键字函数:

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

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 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='foo'):
    print("i am %s" % name)foo()

上面的 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 ('class decorator runing')
        self._func()
        print ('class decorator ending')@Foodef bar():
    print ('bar')bar()

functools.wraps

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

# 装饰器def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'with_logging'
        print func.__doc__       # 输出 None
        return func(*args, **kwargs)
    return with_logging# 函数@loggeddef f(x):
   """does some math"""
   return x + x * xlogged(f)

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

from functools import wrapsdef logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging@loggeddef f(x):
   """does some math"""
   return x + x * x

装饰器顺序

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

@a
@b
@c
def f ():
    pass

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

f = a(b(c(f)))

zhijun liu首发于知乎专栏Python之禅,原文地址:https://zhuanlan.zhihu.com/p/24900548

Déclaration:
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