Quelqu'un peut-il me donner un exemple de la raison pour laquelle la fonction «envoyer» associée à la fonction de générateur Python existe? Je comprends parfaitement la fonction de rendement. Cependant, la fonction d'envoi est déroutante pour moi. La documentation sur cette méthode est compliquée:
generator.send(value)
Reprend l'exécution et «envoie» une valeur dans la fonction générateur. L'argument value devient le résultat de l'expression de rendement actuelle. La méthode send () renvoie la valeur suivante fournie par le générateur, ou lève StopIteration si le générateur se ferme sans donner une autre valeur.
Qu'est-ce que ça veut dire? Je pensais que la valeur était l'entrée de la fonction? La phrase "La méthode send () renvoie la valeur suivante fournie par le générateur" semble être aussi le but exact de la fonction yield; yield renvoie la valeur suivante fournie par le générateur ...
Quelqu'un peut-il me donner un exemple d'un générateur utilisant l'envoi qui accomplit quelque chose que le rendement ne peut pas?
send()
est appelé pour démarrer le générateur, il doit être appelé avecNone
comme argument, car il n'y a pas d'expression yield qui pourrait recevoir la valeur.", Cité dans la documentation officielle et pour laquelle la citation dans la question est manquant.Réponses:
Il est utilisé pour envoyer des valeurs dans un générateur qui vient de produire. Voici un exemple explicatif artificiel (non utile):
Vous ne pouvez pas faire ça uniquement avec
yield
.Quant à savoir pourquoi il est utile, l'un des meilleurs cas d'utilisation que j'ai vus est celui de Twisted
@defer.inlineCallbacks
. Essentiellement, cela vous permet d'écrire une fonction comme celle-ci:Ce qui se passe, c'est que
takesTwoSeconds()
renvoie aDeferred
, qui est une valeur promettant qu'une valeur sera calculée plus tard. Twisted peut exécuter le calcul dans un autre thread. Lorsque le calcul est terminé, il le passe dans le différé, et la valeur est ensuite renvoyée à ladoStuff()
fonction. Ainsi, ledoStuff()
peut finir par ressembler plus ou moins à une fonction procédurale normale, sauf qu'il peut faire toutes sortes de calculs et de rappels, etc. L'alternative avant cette fonctionnalité serait de faire quelque chose comme:C'est beaucoup plus compliqué et compliqué.
la source
Cette fonction consiste à écrire des coroutines
impressions
Voyez comment le contrôle est passé dans les deux sens? Ce sont des coroutines. Ils peuvent être utilisés pour toutes sortes de choses intéressantes comme asynch IO et autres.
Pensez-y comme ça, avec un générateur et pas d'envoi, c'est une rue à sens unique
Mais avec envoyer, ça devient une rue à double sens
Ce qui ouvre la porte à l'utilisateur en personnalisant le comportement des générateurs à la volée et au générateur répondant à l'utilisateur.
la source
send()
le générateur n'a pas encore atteint le mot-cléyield
.Cela peut aider quelqu'un. Voici un générateur qui n'est pas affecté par la fonction d'envoi. Il prend le paramètre number lors de l'instanciation et n'est pas affecté par send:
Maintenant, voici comment vous feriez le même type de fonction en utilisant send, donc à chaque itération, vous pouvez changer la valeur de number:
Voici à quoi cela ressemble, comme vous pouvez le voir, l'envoi d'une nouvelle valeur pour nombre modifie le résultat:
Vous pouvez également mettre ceci dans une boucle for en tant que telle:
Pour plus d'aide, consultez cet excellent tutoriel .
la source
send
? Un simplelambda x: x * 2
fait la même chose d'une manière beaucoup moins compliquée.Quelques cas d'utilisation pour l'utilisation du générateur et
send()
Générateurs avec
send()
allow:Voici quelques cas d'utilisation:
Regardé tentative de suivre une recette
Laissez-nous avoir une recette, qui attend un ensemble prédéfini d'entrées dans un certain ordre.
Nous pouvons:
watched_attempt
instance à partir de la recetteà chaque entrée vérifier, que l'entrée est celle attendue (et échouer si ce n'est pas le cas)
Pour l'utiliser, créez d'abord l'
watched_attempt
instance:L'appel à
.next()
est nécessaire pour démarrer l'exécution du générateur.La valeur retournée montre, notre pot est actuellement vide.
Maintenant, faites quelques actions en suivant ce que la recette attend:
Comme on le voit, le pot est enfin vide.
Dans le cas où on ne suivrait pas la recette, elle échouerait (quel résultat escompté d'une tentative surveillée de cuisiner quelque chose - juste en apprenant que nous n'avons pas prêté suffisamment d'attention lorsqu'on leur a donné des instructions.
Remarquerez que:
Totaux courants
Nous pouvons utiliser le générateur pour suivre le total des valeurs qui lui sont envoyées.
Chaque fois que nous ajoutons un nombre, le nombre d'entrées et la somme totale sont renvoyés (valable pour le moment où l'entrée précédente y a été envoyée).
La sortie ressemblerait à:
la source
La
send()
méthode contrôle la valeur à gauche de l'expression de rendement.Pour comprendre en quoi le rendement diffère et quelle valeur il détient, actualisons d'abord rapidement l'ordre dans lequel le code python est évalué.
Section 6.15 Ordre d'évaluation
Ainsi, une expression
a = b
du côté droit est évaluée en premier.Comme le montre ce qui suit,
a[p('left')] = p('right')
le côté droit est évalué en premier.Que fait yield ?, cède, suspend l'exécution de la fonction et retourne à l'appelant, et reprend l'exécution au même endroit qu'elle avait laissé avant la suspension.
Où exactement l'exécution est-elle suspendue? Vous l'avez peut-être déjà deviné ... l' exécution est suspendue entre le côté droit et gauche de l'expression yield. Ainsi,
new_val = yield old_val
l'exécution est interrompue au=
signe, et la valeur à droite (qui est avant la suspension, et est également la valeur renvoyée à l'appelant) peut être quelque chose de différent de la valeur à gauche (qui est la valeur attribuée après la reprise exécution).yield
donne 2 valeurs, une à droite et une autre à gauche.Comment contrôlez-vous la valeur à gauche de l'expression de rendement? via la
.send()
méthode.6.2.9. Expressions de rendement
la source
La
send
méthode implémente des coroutines .Si vous n'avez pas rencontré Coroutines, ils sont difficiles à comprendre car ils changent la façon dont un programme se déroule. Vous pouvez lire un bon tutoriel pour plus de détails.
la source
Le mot «céder» a deux significations: produire quelque chose (par exemple, donner du maïs) et s'arrêter pour laisser quelqu'un / autre continuer (par exemple, des voitures cédant aux piétons). Les deux définitions s'appliquent au
yield
mot - clé de Python ; ce qui rend les fonctions génératrices spéciales, c'est que contrairement aux fonctions régulières, les valeurs peuvent être "retournées" à l'appelant tout en mettant simplement en pause, et non en terminant, une fonction génératrice.Il est plus facile d'imaginer un générateur comme une extrémité d'un tuyau bidirectionnel avec une extrémité «gauche» et une extrémité «droite»; ce tuyau est le support sur lequel les valeurs sont envoyées entre le générateur lui-même et le corps de la fonction générateur. Chaque extrémité du tube comporte deux opérations
push
:, qui envoie une valeur et bloque jusqu'à ce que l'autre extrémité du tube tire la valeur et ne renvoie rien; etpull
, qui bloque jusqu'à ce que l'autre extrémité du tube pousse une valeur et renvoie la valeur poussée. Au moment de l'exécution, l'exécution rebondit entre les contextes de chaque côté du tube - chaque côté s'exécute jusqu'à ce qu'il envoie une valeur à l'autre côté, à quel point il s'arrête, laisse l'autre côté s'exécuter et attend une valeur dans retour, à quel point l'autre côté s'arrête et il reprend. En d'autres termes, chaque extrémité du tuyau s'étend du moment où il reçoit une valeur au moment où il envoie une valeur.Le tuyau est fonctionnellement symétrique, mais - par convention que je définis dans cette réponse - l'extrémité gauche n'est disponible qu'à l'intérieur du corps de la fonction du générateur et est accessible via le
yield
mot - clé, tandis que l'extrémité droite est le générateur et est accessible via lesend
fonction du générateur . Comme interfaces singulières à leurs extrémités respectives du tube,yield
et unesend
double fonction: ils ont chacun deux poussoirs et les valeurs de traction vers / à partir de leurs extrémités de la conduite,yield
poussant vers la droite et vers la gauche tout en tirantsend
fait le contraire. Ce double devoir est au cœur de la confusion entourant la sémantique des déclarations commex = yield y
. Briseryield
etsend
en deux push / pull étapes explicites fera leur sémantique beaucoup plus claire:g
soit le générateur.g.send
pousse une valeur vers la gauche à travers l'extrémité droite du tuyau.g
pauses, permettant au corps de la fonction génératrice de s'exécuter.g.send
est tirée vers la gaucheyield
et reçue à l'extrémité gauche du tuyau. Inx = yield y
,x
est affecté à la valeur extraite.yield
soit atteinte.yield
pousse une valeur vers la droite à travers l'extrémité gauche du tuyau, vers le hautg.send
. Dansx = yield y
,y
est poussé vers la droite à travers le tuyau.g.send
reprend et extrait la valeur et la renvoie à l'utilisateur.g.send
prochain appel, revenez à l'étape 1.Bien que cyclique, cette procédure a un début: quand
g.send(None)
- ce quinext(g)
est l'abréviation de - est appelée pour la première fois (il est interdit de passer autre chose qu'auNone
premiersend
appel). Et cela peut avoir une fin: quand il n'y a plus d'yield
instructions à atteindre dans le corps de la fonction génératrice.Voyez-vous ce qui rend la
yield
déclaration (ou plus précisément les générateurs) si spéciale? Contrairement aureturn
mot clé maigre ,yield
est capable de transmettre des valeurs à son appelant et de recevoir des valeurs de son appelant sans mettre fin à la fonction dans laquelle il vit! (Bien sûr, si vous souhaitez terminer une fonction - ou un générateur - il est également pratique d'avoir lereturn
mot - clé.) Lorsqu'uneyield
instruction est rencontrée, la fonction de générateur se met simplement en pause, puis reprend là où elle était off lors de l'envoi d'une autre valeur. Etsend
c'est juste l'interface pour communiquer avec l'intérieur d'une fonction de générateur depuis l'extérieur.Si nous voulons vraiment casser cette analogie push / pull / pipe aussi loin que possible, nous nous retrouvons avec le pseudo-code suivant qui nous ramène vraiment à la maison, mis à part les étapes 1 à 5,
yield
et quisend
sont les deux côtés du même tube depièces:right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
La transformation clé est que nous avons divisé
x = yield y
etvalue1 = g.send(value2)
chacun en deux déclarations:left_end.push(y)
etx = left_end.pull()
; etvalue1 = right_end.pull()
etright_end.push(value2)
. Il existe deux cas particuliers duyield
mot - clé:x = yield
etyield y
. Ce sont des sucres syntaxiques, respectivement, pourx = yield None
et_ = yield y # discarding value
.Pour plus de détails sur l'ordre précis dans lequel les valeurs sont envoyées via le canal, voir ci-dessous.
Ce qui suit est un modèle concret assez long de ce qui précède. Tout d'abord, il convient de noter que pour tout générateur
g
,next(g)
est exactement équivalent àg.send(None)
. Dans cet esprit, nous pouvons nous concentrer uniquement sur la façon dontsend
fonctionne et parler uniquement de l'avancement du générateur avecsend
.Supposons que nous ayons
Maintenant, la définition de
f
grossièrement desugars à la fonction ordinaire (non génératrice) suivante:Ce qui suit s'est produit dans cette transformation de
f
:left_end
auquel la fonction imbriquée accédera et dontright_end
la portée externe sera renvoyée et accédée -right_end
c'est ce que nous appelons l'objet générateur.left_end.pull()
c'estNone
consommer une valeur poussée dans le processus.x = yield y
a été remplacée par deux lignes:left_end.push(y)
etx = left_end.pull()
.send
fonction pourright_end
, qui est la contrepartie des deux lignes par lesquelles nous avons remplacé l'x = yield y
instruction à l'étape précédente.Dans ce monde fantastique où les fonctions peuvent continuer après le retour,
g
est assignéright_end
puisimpl()
appelé. Donc, dans notre exemple ci-dessus, si nous suivions l'exécution ligne par ligne, ce qui se passerait est à peu près le suivant:Cela correspond exactement au pseudocode en 16 étapes ci-dessus.
Il y a d'autres détails, comme la façon dont les erreurs se propagent et ce qui se passe lorsque vous atteignez la fin du générateur (le tuyau est fermé), mais cela devrait clarifier le fonctionnement du flux de contrôle de base lorsqu'il
send
est utilisé.En utilisant ces mêmes règles de désuétude, examinons deux cas particuliers:
Pour la plupart, ils désugarent de la même manière que
f
, les seules différences sont la façon dont lesyield
instructions sont transformées:Dans le premier, la valeur transmise à
f1
est poussée (renvoyée) initialement, puis toutes les valeurs extraites (envoyées) sont repoussées (renvoyées) vers l'arrière. Dans le second,x
n'a pas (encore) de valeur quand il arrive pour la première foispush
, donc anUnboundLocalError
est élevé.la source
yield
?send
; il faut un appel desend(None)
pour déplacer le curseur vers la premièreyield
instruction, et alors seulement lessend
appels suivants envoient réellement une valeur "réelle" àyield
.f
serayield
à un moment donné, et attendra donc qu'elle reçoive unsend
de l'appelant? Avec une fonction normale cal, l'interpréteur commencerait tout de suite à s'exécuterf
, non? Après tout, il n'y a aucune compilation AOT d'aucune sorte en Python. Etes-vous sûr que c'est le cas? (sans remettre en question ce que vous dites, je suis vraiment perplexe par ce que vous avez écrit ici). Où puis-je en savoir plus sur la façon dont Python sait qu'il doit attendre avant de commencer à exécuter le reste de la fonction?send(None)
donne la valeur appropriée (par exemple1
) sans envoyerNone
dans le générateur suggère que le premier appel àsend
est un cas particulier. C'est une interface délicate à concevoir; si vous laissez le premiersend
envoyer une valeur arbitraire, alors l'ordre des valeurs produites et des valeurs envoyées serait décalé de un par rapport à ce qu'il est actuellement.Celles-ci me confondaient aussi. Voici un exemple que j'ai fait en essayant de mettre en place un générateur qui cède et accepte des signaux dans un ordre alterné (rendement, acceptation, rendement, acceptation) ...
La sortie est:
la source