Maison  >  Article  >  développement back-end  >  Explication détaillée du rendement de la syntaxe dans Python 3

Explication détaillée du rendement de la syntaxe dans Python 3

高洛峰
高洛峰original
2017-02-21 10:11:001347parcourir

Dans python 3.3, le générateur a une nouvelle syntaxe rendement from. Quelle est la fonction de rendement from ? Quelle est la syntaxe ? L'article suivant vous présente principalement en détail les informations pertinentes sur la syntaxe du rendement dans Python 3. Les amis qui en ont besoin peuvent s'y référer.

Préface

Je bricolais Autobahn récemment, et il a donné un exemple basé sur asyncio. J'ai pensé à le mettre sur pypy3 et à l'exécuter, mais c'est... . échoué. . pip install asyncioJ'ai signalé directement une syntaxe invalide. À première vue, je pensais qu'il y avait un problème avec le traitement 2to3 - vous ne pouvez pas me blâmer, de nombreux packages ont été écrits en 2 puis convertis en 3 - il s'est avéré que c'était uniquement asyncio. prend en charge la version 3.3., puis j'ai regardé le code, j'ai soudainement trouvé une phrase yield from je sais, mais yield est un cheval magique ? yield from

PEP-380

Eh bien, j'ai trouvé ce titre via Google. La vie passée et présente de

est toute dans ce PEP. l'idée générale est celle d'origineyield fromL'instruction ne peut rendre le contrôle du CPU qu'à l'appelant direct. Lorsque vous souhaitez reconstruire la logique avec une instruction rendement dans un générateur ou une coroutine dans un autre générateur (sous-générateur d'origine), ce sera très. gênant, car à l'extérieur Le générateur est responsable du passage des messages pour le générateur à l'intérieur ; donc quelqu'un a eu l'idée de laisser Python encapsuler le message qui passe pour le rendre transparent pour les programmeurs, donc il y avait yield. yield from

PEP-380 spécifie la sémantique de

, ou le modèle de comportement que devraient avoir les générateurs imbriqués. yield from

Supposons qu'il existe une telle instruction dans la fonction A

yield from B()

renvoie un objet itérable (itérable) b , alors A () renverra un générateur - selon notre convention de dénomination, le nom est a - puis : B()

  1. Chaque valeur générée par l'itération b est directement transmise à l'appel de a who.

  2. Toutes les valeurs envoyées à a via la méthode d'envoi sont transmises directement à b. Si la valeur envoyée est Aucune, la méthode

    de b est appelée, sinon la méthode d'envoi. de b est appelé . Si l'appel de méthode à b génère une exception StopIteration, a continuera à exécuter les instructions qui suivent __next__(), et d'autres exceptions seront propagées à a, obligeant a à lever une exception lors de l'exécution de yield from. yield from

  3. Si une exception autre que GeneratorExit est lancée dans a, l'exception sera lancée directement dans b. Si la méthode throw de b lance StopIteration, a continuera à s'exécuter ; d'autres exceptions entraîneront également la levée d'une exception par a.

  4. Si une exception GeneratorExit est levée dans a, ou si la méthode close de a est appelée, et que b a également une méthode close, la méthode close de b sera également appelée. Si cette méthode de b lève une exception, a lancera également une exception. Au contraire, si b est fermé avec succès, a lèvera également une exception, mais il s'agit d'une exception GeneratorExit spécifique.

  5. Le résultat de l'évaluation de l'expression

    dans a est le premier paramètre de l'exception StopIteration levée à la fin de l'itération de b. yield from

  6. L'instruction

    dans b lancera en fait une exception return 942bcdedd51e2f7d833cc5298f53fcca, donc la valeur de return dans b deviendra la valeur de retour de l'expression StopIteration(942bcdedd51e2f7d833cc5298f53fcca) dans a. yield from

Pourquoi Shenma a-t-il autant d'exigences ? Parce que le comportement des générateurs devient très compliqué après l'ajout de la méthode throw, surtout lorsque plusieurs générateurs sont ensemble, des primitives similaires à la gestion des processus sont nécessaires pour les faire fonctionner. Toutes les exigences ci-dessus visent à unifier le comportement intrinsèquement complexe du générateur, il ne peut donc naturellement pas être simplifié.

J’avoue que je n’ai pas compris ce que l’auteur du PEP voulait dire, donc il pourrait être utile de le « reconstruire ».

Un exemple inutile

C'est inutile car vous n'avez probablement pas vraiment envie d'écrire le programme comme ça, mais... Quoi qu'il en soit, c'est suffisant pour illustrer le problème .

Imaginez une fonction génératrice comme celle-ci :

def inner():
 coef = 1
 total = 0
 while True:
 try:
  input_val = yield total
  total = total + coef * input_val
 except SwitchSign:
  coef = -(coef)
 except BreakOut:
  return total

Le générateur généré par cette fonction accumulera la valeur reçue de la méthode d'envoi pour le total de la variable locale et arrête l'itération lorsqu'une exception BreakOut est reçue ; comme pour l'autre exception SwitchSign, elle ne devrait pas être difficile à comprendre, donc je ne la gâcherai pas ici.

Du point de vue du code, le générateur obtenu par la fonction

reçoit les données à utiliser via l'envoi, et accepte en même temps le contrôle du code externe via la méthode throw pour exécuter différentes branches de code. est très clair jusqu'à présent. inner()

Ensuite, en raison de changements dans les exigences, nous devons ajouter le code d'initialisation et de nettoyage du site avant et après le code

. Puisque je pense "ne touchez pas au code qui n'est pas cassé", j'ai décidé de laisser inner() tel quel, puis d'en écrire un autre inner(), de mettre le code ajouté à l'intérieur de outer() et de fournir les mêmes opérations que outer() interfaces. Puisque inner() utilise plusieurs fonctionnalités du générateur, inner() doit également faire ces cinq choses : outer()

  1. outer()必须生成一个generator;

  2. 在每一步的迭代中,outer()要帮助inner()返回迭代值;

  3. 在每一步的迭代中,outer()要帮助inner()接收外部发送的数据;

  4. 在每一步的迭代中,outer()要处理inner()接收和抛出所有异常;

  5. outer()被close的时候,inner()也要被正确地close掉。

根据上面的要求,在只有yield的世界里,outer()可能是长这样的:

def outer1():
 print("Before inner(), I do this.")
 i_gen = inner()
 input_val = None
 ret_val = i_gen.send(input_val)
 while True:
 try:
  input_val = yield ret_val
  ret_val = i_gen.send(input_val)
 except StopIteration:
  break
 except Exception as err:
  try:
  ret_val = i_gen.throw(err)
  except StopIteration:
  break
 print("After inner(), I do that.")

WTF,这段代码比inner()本身还要长,而且还没处理close操作。

现在我们来试试外星科技:

def outer2():
 print("Before inner(), I do this.")
 yield from inner()
 print("After inner(), I do that.")

除了完全符合上面的要求外,这四行代码打印出来的时候还能省点纸。

我们可以在outer1()outer2()上分别测试 数据 以及 异常 的传递,不难发现这两个generator的行为基本上是一致的。既然如此, 外星科技当然在大多数情况下是首选。

对generator和coroutine的疑问

从以前接触到Python下的coroutine就觉得它怪怪的,我能看清它们的 行为模式,但是并不明白为什么要使用这种模式,generator和 coroutine具有一样的对外接口,是generator造就了coroutine呢,还 是coroutine造就了generator?最让我百思不得其解的是,Python下 的coroutine将“消息传递”和“调度”这两种操作绑在一个yield 上——即便有了yield from,这个状况还是没变过——我看不出这样做 的必要性。如果一开始就从语法层面将这两种语义分开,并且为 generator和coroutine分别设计一套接口,coroutine的概念大概也会 容易理解一些。

更多Python 3中的yield from语法详解相关文章请关注PHP中文网!

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