J'ai du mal à enrouler mon cerveau autour de PEP 380 .
- Dans quelles situations le «rendement de» est-il utile?
- Quel est le cas d'utilisation classique?
- Pourquoi est-il comparé aux micro-threads?
[ mise à jour ]
Maintenant, je comprends la cause de mes difficultés. J'ai utilisé des générateurs, mais je n'ai jamais vraiment utilisé de coroutines (introduit par PEP-342 ). Malgré certaines similitudes, les générateurs et les coroutines sont fondamentalement deux concepts différents. Comprendre les coroutines (pas seulement les générateurs) est la clé pour comprendre la nouvelle syntaxe.
Les coroutines à mon humble avis sont la fonctionnalité Python la plus obscure , la plupart des livres la rendent inutile et sans intérêt.
Merci pour les bonnes réponses, mais merci tout particulièrement à agf et à son commentaire en lien avec les présentations de David Beazley . David bascule.
Réponses:
Éliminons d'abord une chose. L'explication qui
yield from g
équivaut àfor v in g: yield v
ne commence même pas à rendre justice àyield from
tout. Car, avouons-le, si tout ceyield from
qui est fait est d'étendre lafor
boucle, alors cela ne justifie pas d'ajouteryield from
au langage et d'empêcher tout un tas de nouvelles fonctionnalités d'être implémentées dans Python 2.x.Qu'est
yield from
- ce qu'il établit est une connexion bidirectionnelle transparente entre l'appelant et le sous-générateur :La connexion est "transparente" dans le sens où elle propagera tout aussi correctement, pas seulement les éléments générés (par exemple, les exceptions sont propagées).
La connexion est "bidirectionnelle" dans le sens où les données peuvent être envoyées depuis et vers un générateur.
( Si nous parlions de TCP, cela
yield from g
pourrait signifier "déconnecter temporairement le socket de mon client et le reconnecter à cet autre socket de serveur". )BTW, si vous n'êtes pas sûr de ce que signifie envoyer des données à un générateur , vous devez tout supprimer et lire d' abord sur les coroutines - elles sont très utiles (contrastez-les avec les sous-routines ), mais malheureusement moins connues en Python. Curious Course sur Coroutines de Dave Beazley est un excellent début. Lisez les diapositives 24 à 33 pour une introduction rapide.
Lecture des données d'un générateur en utilisant le rendement de
Au lieu d'itérer manuellement
reader()
, nous pouvons le faireyield from
.Cela fonctionne et nous avons éliminé une ligne de code. Et l'intention est probablement un peu plus claire (ou pas). Mais rien ne change la vie.
Envoi de données à un générateur (coroutine) en utilisant le rendement de - Partie 1
Faisons maintenant quelque chose de plus intéressant. Créons une coroutine appelée
writer
qui accepte les données qui lui sont envoyées et écrit dans un socket, fd, etc.Maintenant, la question est de savoir comment la fonction d'encapsuleur doit gérer l'envoi de données à l'enregistreur, de sorte que toutes les données envoyées à l'encapsuleur soient envoyées de manière transparente au
writer()
?L'encapsuleur doit accepter les données qui lui sont envoyées (évidemment) et doit également gérer le
StopIteration
moment où la boucle for est épuisée. Évidemment, fairefor x in coro: yield x
ne suffit pas. Voici une version qui fonctionne.Ou, nous pourrions le faire.
Cela enregistre 6 lignes de code, le rend beaucoup plus lisible et cela fonctionne. La magie!
Envoi de données à un générateur de rendement à partir de - Partie 2 - Gestion des exceptions
Rendons les choses plus compliquées. Et si notre rédacteur devait gérer les exceptions? Disons que les
writer
poignées aSpamException
et elles s'impriment***
si elles en rencontrent une.Et si on ne change pas
writer_wrapper
? Est-ce que ça marche? EssayonsHum, ça ne marche pas parce
x = (yield)
que lève juste l'exception et tout s'arrête brutalement. Faisons-le fonctionner, mais en gérant manuellement les exceptions et en les envoyant ou en les jetant dans le sous-générateur (writer
)Cela marche.
Mais ça aussi!
Le
yield from
gère de manière transparente l'envoi des valeurs ou le lancement de valeurs dans le sous-générateur.Cependant, cela ne couvre pas tous les cas d'angle. Que se passe-t-il si le générateur extérieur est fermé? Qu'en est-il du cas où le sous-générateur renvoie une valeur (oui, en Python 3.3+, les générateurs peuvent renvoyer des valeurs), comment la valeur de retour doit-elle être propagée? La
yield from
gestion transparente de tous les cas d'angle est vraiment impressionnante .yield from
fonctionne comme par magie et gère tous ces cas.Personnellement, je pense que
yield from
c'est un mauvais choix de mots clés car cela ne rend pas la nature bidirectionnelle apparente. D'autres mots clés ont été proposés (commedelegate
mais ont été rejetés car l'ajout d'un nouveau mot clé à la langue est beaucoup plus difficile que de combiner les mots clés existants.En résumé, il est préférable de penser
yield from
commetransparent two way channel
entre l'appelant et le sous-générateur.Références:
la source
except StopIteration: pass
intérieur de lawhile True:
boucle n'est pas une représentation précise deyield from coro
- qui n'est pas une boucle infinie et après avoircoro
été épuisé (c'est-à-dire déclenche StopIteration),writer_wrapper
exécutera l'instruction suivante. Après la dernière déclaration, il seStopIteration
writer
contenu à lafor _ in range(4)
place dewhile True
, puis après l'impression,>> 3
il serait également augmenté automatiquementStopIteration
et cela serait géré automatiquement paryield from
, puiswriter_wrapper
augmenterait automatiquement le sienStopIteration
et parce qu'ilwrap.send(i)
n'est pas dans letry
bloc, il serait en fait élevé à ce stade ( c'est-à-dire que traceback ne rapportera que la ligne avecwrap.send(i)
, rien de l'intérieur du générateur)Chaque situation où vous avez une boucle comme celle-ci:
Comme le décrit le PEP, il s'agit d'une tentative plutôt naïve d'utiliser le sous-générateur, il manque plusieurs aspects, en particulier la bonne gestion des mécanismes
.throw()
/.send()
/.close()
introduits par le PEP 342 . Pour faire cela correctement, un code assez compliqué est nécessaire.Considérez que vous souhaitez extraire des informations d'une structure de données récursive. Disons que nous voulons obtenir tous les nœuds feuilles dans un arbre:
Plus important encore est le fait que jusqu'à la
yield from
, il n'y avait pas de méthode simple pour refactoriser le code du générateur. Supposons que vous ayez un générateur (insensé) comme celui-ci:Vous décidez maintenant de factoriser ces boucles dans des générateurs distincts. Sans
yield from
, c'est moche, au point où vous réfléchirez à deux fois si vous voulez vraiment le faire. Avecyield from
, c'est vraiment agréable de regarder:Je pense que ce dont parle cette section du PEP, c'est que chaque générateur a son propre contexte d'exécution isolé. Associé au fait que l'exécution est commutée entre le générateur-itérateur et l'appelant à l'aide
yield
et__next__()
respectivement, cela est similaire aux threads, où le système d'exploitation commute de temps en temps le thread d'exécution, ainsi que le contexte d'exécution (pile, registres, ...).L'effet est également comparable: le générateur-itérateur et l'appelant progressent dans leur état d'exécution en même temps, leurs exécutions sont entrelacées. Par exemple, si le générateur effectue une sorte de calcul et que l'appelant imprime les résultats, vous verrez les résultats dès qu'ils seront disponibles. Il s'agit d'une forme de concurrence.
Cette analogie n'est pas quelque chose de spécifique
yield from
, cependant - c'est plutôt une propriété générale des générateurs en Python.la source
get_list_values_as_xxx
sont de simples générateurs avec une seule lignefor x in input_param: yield int(x)
et les deux autres respectivement avecstr
etfloat
Chaque fois que vous invoquez un générateur de « pompe » dans un générateur , vous avez besoin d' une pour re
yield
les valeurs suivantes :for v in inner_generator: yield v
. Comme le PEP le souligne, il existe des complexités subtiles à cela que la plupart des gens ignorent. Un contrôle de flux non local commethrow()
un exemple est donné dans le PEP. La nouvelle syntaxeyield from inner_generator
est utilisée partout où vous auriez écrit lafor
boucle explicite auparavant. Ce n'est pas seulement du sucre syntaxique, cependant: il gère tous les cas d'angle qui sont ignorés par lafor
boucle. Être «sucré» encourage les gens à l'utiliser et à adopter ainsi les bons comportements.Ce message dans le fil de discussion parle de ces complexités:
Je ne peux pas parler de comparaison avec des micro-threads, si ce n'est d'observer que les générateurs sont une sorte de paralellisme. Vous pouvez considérer le générateur suspendu comme un thread qui envoie des valeurs via
yield
un thread consommateur. L'implémentation réelle peut ne rien ressembler à cela (et l'implémentation réelle est évidemment d'un grand intérêt pour les développeurs Python) mais cela ne concerne pas les utilisateurs.La nouvelle
yield from
syntaxe n'ajoute aucune capacité supplémentaire au langage en termes de thread, elle facilite simplement l'utilisation correcte des fonctionnalités existantes. Ou plus précisément, il permet au consommateur novice d'un générateur interne complexe écrit par un expert de passer à travers ce générateur sans casser aucune de ses fonctionnalités complexes.la source
Un court exemple vous aidera à comprendre l'un des
yield from
cas d'utilisation: obtenir la valeur d'un autre générateurla source
print(*flatten([1, [2], [3, [4]]]))
yield from
enchaîne essentiellement les itérateurs de manière efficace:Comme vous pouvez le voir, il supprime une boucle Python pure. C'est à peu près tout ce qu'il fait, mais le chaînage des itérateurs est un modèle assez courant en Python.
Les threads sont essentiellement une fonctionnalité qui vous permet de sauter des fonctions à des points complètement aléatoires et de revenir à l'état d'une autre fonction. Le superviseur de thread fait cela très souvent, donc le programme semble exécuter toutes ces fonctions en même temps. Le problème est que les points sont aléatoires, vous devez donc utiliser le verrouillage pour empêcher le superviseur d'arrêter la fonction à un point problématique.
Les générateurs sont assez similaires aux threads dans ce sens: ils vous permettent de spécifier des points spécifiques (chaque fois qu'ils
yield
) où vous pouvez sauter. Lorsqu'ils sont utilisés de cette façon, les générateurs sont appelés coroutines.Lisez cet excellent tutoriel sur les coroutines en Python pour plus de détails
la source
throw()/send()/close()
doivent évidemment être correctement implémentées car elles sont censées simplifier le code. De telles banalités n'ont rien à voir avec l'usage.yield
yield from
chain
fonction car elleitertools.chain
existe déjà. Utilisezyield from itertools.chain(*iters)
.En utilisation appliquée pour la coroutine d'E / S asynchrone ,
yield from
a un comportement similaireawait
à celui d'une fonction de coroutine . Les deux sont utilisés pour suspendre l'exécution de la coroutine.yield from
est utilisé par la coroutine basée sur le générateur .await
est utilisé pour laasync def
coroutine. (depuis Python 3.5+)Pour Asyncio, s'il n'est pas nécessaire de prendre en charge une ancienne version de Python (ie> 3.5),
async def
/await
est la syntaxe recommandée pour définir une coroutine. Ainsiyield from
n'est plus nécessaire dans une coroutine.Mais en général en dehors de asyncio,
yield from <sub-generator>
a encore un autre usage dans l'itération du sous-générateur comme mentionné dans la réponse précédente.la source
Ce code définit une fonction
fixed_sum_digits
renvoyant un générateur énumérant les six chiffres tels que la somme des chiffres est 20.Essayez de l'écrire sans
yield from
. Si vous trouvez un moyen efficace de le faire, faites-le moi savoir.Je pense que pour des cas comme celui-ci: visiter des arbres,
yield from
rend le code plus simple et plus propre.la source
En termes simples,
yield from
fournit une récursivité de queue pour les fonctions d'itérateur.la source