Mettons d'abord une chose au clair. L'explication que yield from g
est équivalent à for v in g: yield v
ne commence même pas à rendre justice à quoi yield from
est tout ce qui compte. Parce que, regardons les choses en face, si tout yield from
permet d'étendre le for
alors il n'est pas nécessaire d'ajouter yield from
au langage et empêchent l'implémentation d'un grand nombre de nouvelles fonctionnalités dans Python 2.x.
Quoi yield from
fait est il établit une connexion bidirectionnelle transparente entre l'appelant et le sous-générateur :
-
La connexion est "transparente" dans le sens où elle propage également tout ce qui est correct, et 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 à la fois envoyées de y à un générateur.
( Si nous parlions de TCP, yield from g
pourrait signifier "maintenant déconnecter temporairement la socket de mon client et la reconnecter à cette autre socket du serveur". )
BTW, si vous n'êtes pas sûr de ce que envoi de données à un générateur même signifie, que vous devez tout laisser tomber et lire sur coroutines tout d'abord, ils sont très utiles (contraste avec les sous-programmes ), mais malheureusement moins connu en Python. Le cours curieux de Dave Beazley sur les coroutines est un excellent début. Lire les diapositives 24-33 pour une amorce rapide.
Lecture des données d'un générateur en utilisant le rendement de
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
Au lieu d'itérer manuellement sur reader()
on peut juste yield from
il.
def reader_wrapper(g):
yield from g
Cela fonctionne, et nous avons éliminé une ligne de code. Et l'intention est probablement un peu plus claire (ou pas). Mais rien qui ne change la vie.
Envoyer des données à un générateur (coroutine) en utilisant yield from - Partie 1
Maintenant, faisons 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.
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Maintenant, la question est de savoir comment la fonction wrapper doit gérer l'envoi de données au rédacteur, de sorte que toute donnée envoyée au wrapper soit de manière transparente envoyé à la writer()
?
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
Le wrapper doit accepter les données qui lui sont envoyées (évidemment) et doit également traiter les StopIteration
lorsque la boucle for est épuisée. De toute évidence, il suffit de faire for x in coro: yield x
ne fonctionne pas. Voici une version qui fonctionne.
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
Ou, on pourrait faire ça.
def writer_wrapper(coro):
yield from coro
Cela permet d'économiser 6 lignes de code, de le rendre beaucoup plus lisible et de le faire fonctionner. Magique !
Envoi de données à un générateur de rendement de - Partie 2 - Traitement des exceptions
Rendons les choses plus compliquées. Et si notre rédacteur doit gérer des exceptions ? Disons que le writer
manipule un SpamException
et il imprime ***
s'il en rencontre un.
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Et si nous ne changeons pas writer_wrapper
? Est-ce que ça marche ? Essayons
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
Um, ça ne marche pas parce que x = (yield)
lève juste l'exception et tout s'arrête net. Faisons en sorte que cela fonctionne, mais en traitant manuellement les exceptions et en les envoyant ou en les jetant dans le sous-générateur ( writer
)
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
Ça marche.
# Result
>> 0
>> 1
>> 2
***
>> 4
Mais ça aussi !
def writer_wrapper(coro):
yield from coro
El yield from
gère de manière transparente l'envoi des valeurs ou le lancement des valeurs dans le sous-générateur.
Mais cela ne couvre pas encore tous les cas de figure. Que se passe-t-il si le générateur externe est fermé ? Et dans le 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 ? Ce yield from
gère de manière transparente tous les cas de figure, c'est vraiment impressionnant. . yield from
fonctionne comme par magie et gère tous ces cas.
Je pense personnellement yield from
est un mauvais choix de mot-clé parce qu'il ne fait pas de la bidirectionnel nature apparente. D'autres mots-clés ont été proposés (comme delegate
mais ont été rejetés parce qu'il est beaucoup plus difficile d'ajouter un nouveau mot clé à la langue que de combiner des mots clés existants.
En résumé, il est préférable de penser à yield from
en tant que transparent two way channel
entre l'appelant et le sous-générateur.
Références :
-
PEP 380 - Syntaxe pour déléguer à un sous-générateur (Ewing) [v3.3, 2009-02-13]
-
PEP 342 - Coroutines via des générateurs améliorés (GvR, Eby) [v2.5, 2005-05-10]
37 votes
dabeaz.com/coroutines
16 votes
Vidéo de l'intervention de David Beazley dabeaz.com/coroutines présentation : youtube.com/watch?v=Z_OAlIhXziw