Résumé de l'utilisation des décorateurs en Python

零下一度
Libérer: 2017-06-29 15:34:32
original
1189 Les gens l'ont consulté

J'apprends Python récemment. Voici ce qui a été introduit dans le groupe d'apprentissage Python. Apprendre maintenant et pratiquer davantage sont de bons moyens d'apprendre. J'espère que tout le monde l'aimera

Python a beaucoup de puissance. et des fonctionnalités attentionnées, si vous souhaitez répertorier les classements les plus populaires, les décorateurs en feront certainement partie.

Lorsque vous rencontrerez la décoratrice pour la première fois, vous vous sentirez élégant et magique. Mais lorsque vous voudrez le réaliser de vos propres mains, vous ressentirez toujours un sentiment de distance, tout comme la beauté de la glace dans le boudoir. Cela est souvent dû au fait que d’autres concepts sont mélangés lors de la compréhension des décorateurs. Lorsque j’enlève les couches de voile, vous verrez que la décoration pure est en réalité assez simple et directe.

Le principe du décorateur

Exécutez un exemple de décorateur sous l'interprète pour le ressentir intuitivement.
# make_bold est le décorateur, la méthode d'implémentation est omise ici


>>> @make_bold
... def get_content():
...  return 'hello world'
...
>>> get_content()
&#39;<b>hello world</b>&#39;
Copier après la connexion

get_content décoré par make_bold, le résultat renvoyé après l'appel sera automatiquement étiqueté avec b Couverture. Comment faire ? Vous pouvez le comprendre en 4 étapes simples.

1. La fonction est un objet

Nous définissons une fonction get_content. À l'heure actuelle, get_content est également un objet et il peut effectuer des opérations sur tous les objets.


def get_content():
  return &#39;hello world&#39;
Copier après la connexion

Il a un id , un type et une valeur.


>>> id(get_content)
140090200473112
>>> type(get_content)
<class &#39;function&#39;>
>>> get_content
<function get_content at 0x7f694aa2be18>
Copier après la connexion

peut être affecté à d'autres variables comme d'autres objets.


>>> func_name = get_content
>>> func_name()
&#39;hello world&#39;
Copier après la connexion

Il peut être passé en paramètre ou en valeur de retour


>>> def foo(bar):
...   print(bar())
...   return bar
...
>>> func = foo(get_content)
hello world
>>> func()
&#39;hello world&#39;
Copier après la connexion

2. Fonction personnaliséeobjet

Nous pouvons utiliser la classe pour construire une fonctionobjet. L'objet fonction qui a l'appel de fonction membre est l'objet fonction qui est appelé lorsque l'objet fonction est appelé.


class FuncObj(object):
  def init(self, name):
    print(&#39;Initialize&#39;)
    self.name= name

  def call(self):
    print(&#39;Hi&#39;, self.name)
Copier après la connexion

Appelons-le. Comme vous pouvez le voir, l'utilisation des objets fonction est divisée en deux étapes : la construction et l'appel (étudiants, faites attention, c'est le point de test).


>>> fo = FuncObj(&#39;python&#39;)
Initialize
>>> fo()
Hi python
Copier après la connexion

3. @ est du sucre syntaxique

Le @ du décorateur ne fait rien de spécial, vous pouvez réaliser la même chose sans cette fonctionnalité, cela nécessite juste plus de code.


@make_bold
def get_content():
  return &#39;hello world&#39;

# 上面的代码等价于下面的

def get_content():
  return &#39;hello world&#39;
get_content = make_bold(get_content)
Copier après la connexion

make_bold est une fonction, exigeant que le paramètre d'entrée soit un objet fonction et que la valeur de retour soit un objet fonction. Le sucre syntaxique de @ élimine en fait la dernière ligne de code ci-dessus, la rendant plus lisible. Après avoir utilisé le décorateur, chaque fois que get_content est appelé, l'objet fonction renvoyé par make_bold est réellement appelé.

4. Utilisez des classes pour implémenter des décorateurs

Le paramètre d'entrée est un objet fonction et le paramètre de retour est un objet fonction si le constructeur de la classe à l'étape 2 est modifié en entrée. Le paramètre étant un objet fonction, n'est-ce pas ? Cela correspond-il exactement à la facture ? Essayons d'implémenter make_bold.


class make_bold(object):
  def init(self, func):
    print(&#39;Initialize&#39;)
    self.func = func

  def call(self):
    print(&#39;Call&#39;)
    return &#39;<b>{}</b>&#39;.format(self.func())
Copier après la connexion

C'est fait, voyons si ça marche.


>>> @make_bold
... def get_content():
...   return &#39;hello world&#39;
...
Initialize
>>> get_content()
Call
&#39;<b>hello world</b>&#39;
Copier après la connexion
Copier après la connexion

Décorateur implémenté avec succès ! N'est-ce pas très simple ?

Voici une analyse des deux processus de construction et d'appel qui ont été soulignés précédemment. Supprimons le sucre de syntaxe @ pour le rendre plus facile à comprendre.
# Construction, lorsque vous utilisez le décorateur pour construire l'objet fonction, appelez init


>>> get_content = make_bold(get_content)
Initialize

# 调用,实际上直接调用的是make_bold构造出来的函数对象
>>> get_content()
Call
&#39;<b>hello world</b>&#39;
Copier après la connexion

C'est tout à fait clair à ce stade, et vous pouvez terminer répandre des fleurs. Fermez la page Web~~~(Si vous voulez juste connaître le principe des décorateurs)

Version fonctionnelle du décorateur

Lors de la lecture du code source, vous voyez souvent l'utilisation de fonctions imbriquées. Comment comprendre le décorateur implémenté ? Cela ne prend que 4 étapes.

1. Initialisation de l'objet fonction de def

Il est facile de voir quand l'objet fonction implémenté avec la classe est construit, mais quand l'objet fonction défini par def est-il construit ?
# Les variables globales ici ont supprimé le contenu non pertinent


>>> globals()
{}
>>> def func():
...   pass
...
>>> globals()
{&#39;func&#39;: <function func at 0x10f5baf28>}
Copier après la connexion

Contrairement à certains langages compilés, la fonction a été construite lorsque le programme démarre correctement. Comme vous pouvez le voir dans l'exemple ci-dessus, un objet fonction est construit jusqu'à ce que def soit exécuté et affecté à la variable make_bold.

L'effet de ce code est très similaire au code ci-dessous.


class NoName(object):
  def call(self):
    pass

func = NoName()
Copier après la connexion

2. Fonctions imbriquées

Les fonctions Python peuvent être des définitions imbriquées.


def outer():
  print(&#39;Before def:&#39;, locals())
  def inner():
    pass
  print(&#39;After def:&#39;, locals())
  return inner
Copier après la connexion

inner est défini dans external, il est donc compté comme une variable locale de external. L'objet fonction n'est pas créé tant que def inner n'est pas exécuté, donc chaque fois que external est appelé, un nouveau inner sera créé. Comme on peut le voir ci-dessous, l’intérieur restitué à chaque fois est différent.


>>> outer()
Before def: {}
After def: {&#39;inner&#39;: <function outer.<locals>.inner at 0x7f0b18fa0048>}
<function outer.<locals>.inner at 0x7f0b18fa0048>
>>> outer()
Before def: {}
After def: {&#39;inner&#39;: <function outer.<locals>.inner at 0x7f0b18fa00d0>}
<function outer.<locals>.inner at 0x7f0b18fa00d0>
Copier après la connexion

3. Fermetures

Quelle est la particularité des fonctions imbriquées ? Parce qu'il y a une fermeture.


def outer():
  msg = &#39;hello world&#39;
  def inner():
    print(msg)
  return inner
Copier après la connexion

Le test suivant montre que l'intérieur peut accéder à la variable locale msg de l'extérieur.


>>> func = outer()
>>> func()
hello world
Copier après la connexion

Les fermetures ont deux caractéristiques
1. L'intérieur peut accéder aux variables (variables locales, Paramètres de fonction).
2. L'appel à external a été renvoyé, mais son espace de noms est référencé par l'objet interne renvoyé, il ne sera donc pas encore recyclé.

Si vous souhaitez approfondir cette partie, vous pouvez en apprendre davantage sur les règles LEGB de Python.

4. Utilisez des fonctions pour implémenter des décorateurs

Le décorateur exige que le paramètre d'entrée soit un objet fonction et que la valeur de retour soit un objet fonction.


def make_bold(func):
  print(&#39;Initialize&#39;)
  def wrapper():
    print(&#39;Call&#39;)
    return &#39;<b>{}</b>&#39;.format(func())
  return wrapper
Copier après la connexion

用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。


>>> @make_bold
... def get_content():
...   return &#39;hello world&#39;
...
Initialize
>>> get_content()
Call
&#39;<b>hello world</b>&#39;
Copier après la connexion
Copier après la connexion

因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。

到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)

常见问题

1. 怎么实现带参数的装饰器?

带参数的装饰器,有时会异常的好用。我们看个例子。


>>> @make_header(2)
... def get_content():
...   return &#39;hello world&#39;
...
>>> get_content()
&#39;<h2>hello world</h2>&#39;
Copier après la connexion

怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。


@make_header(2)
def get_content():
  return &#39;hello world&#39;

# 等价于

def get_content():
  return &#39;hello world&#39;
unnamed_decorator = make_header(2)
get_content = unnamed_decorator(get_content)
Copier après la connexion

上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。

来看一下实现的代码。


def make_header(level):
  print(&#39;Create decorator&#39;)

  # 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level
  def decorator(func):
    print(&#39;Initialize&#39;)
    def wrapper():
      print(&#39;Call&#39;)
      return &#39;<h{0}>{1}</h{0}>&#39;.format(level, func())
    return wrapper

  # make_header返回装饰器
  return decorator
Copier après la connexion

看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。


>>> @make_header(2)
... def get_content():
...   return &#39;hello world&#39;
...
Create decorator
Initialize
>>> get_content()
Call
&#39;<h2>hello world</h2>&#39;
Copier après la connexion

2. 如何装饰有参数的函数

为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。


@make_bold
def get_login_tip(name):
  return &#39;Welcome back, {}&#39;.format(name)
Copier après la connexion

最直接的想法是把 get_login_tip 的参数透传下去。


class make_bold(object):
  def init(self, func):
    self.func = func

  def call(self, name):
    return &#39;<b>{}</b>&#39;.format(self.func(name))
Copier après la connexion

如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是 make_bold 明显不是这种场景。它既需要装饰没有参数的 get_content ,又需要装饰有参数的 get_login_tip 。这时候就需要可变参数了。


class make_bold(object):
  def init(self, func):
    self.func = func
  def call(self, *args, **kwargs):
    return &#39;<b>{}</b>&#39;.format(self.func(*args, **kwargs))
Copier après la connexion

当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。

3. 一个函数能否被多个装饰器装饰?

下面这么写合法吗?


@make_italic
@make_bold
def get_content():
  return &#39;hello world&#39;
Copier après la connexion

合法。上面的的代码和下面等价,留意一下装饰的顺序。


def get_content():
  return &#39;hello world&#39;
get_content = make_bold(get_content) # 先装饰离函数定义近的
get_content = make_italic(get_content)
Copier après la connexion

4. functools.wraps 有什么用?

Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。

为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上, functools.wraps 出场了。它能用于把被调用函数的 module , name , qualname , doc , annotations 赋值给装饰器返回的函数对象。


import functools

def make_bold(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    return &#39;<b>{}</b>&#39;.format(func(*args, **kwargs))
  return wrapper
Copier après la connexion

对比一下效果。


>>> @make_bold
... def get_content():
...   &#39;&#39;&#39;Return page content&#39;&#39;&#39;
...   return &#39;hello world&#39;

# 不用functools.wraps的结果
>>> get_content.name
&#39;wrapper&#39;
>>> get_content.doc
>>>

# 用functools.wraps的结果
>>> get_content.name
&#39;get_content&#39;
>>> get_content.doc
&#39;Return page content&#39;
Copier après la connexion

实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上 functools.wraps 吧。

这次是真·完结了,撒花吧~~~

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
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!