Pour comprendre ce qui yield
fonctionne, vous devez comprendre ce que sont les générateurs . Et avant de pouvoir comprendre les générateurs, vous devez comprendre les itérables .
Iterables
Lorsque vous créez une liste, vous pouvez lire ses éléments un par un. La lecture de ses éléments un par un est appelée itération:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
est un itérable . Lorsque vous utilisez une compréhension de liste, vous créez une liste, et donc un itérable:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Tout ce que vous pouvez utiliser sur " for... in...
" est un itérable; lists
, strings
, Fichiers ...
Ces itérables sont pratiques car vous pouvez les lire autant que vous le souhaitez, mais vous stockez toutes les valeurs en mémoire et ce n'est pas toujours ce que vous voulez quand vous avez beaucoup de valeurs.
Générateurs
Les générateurs sont des itérateurs, une sorte d'itérable que vous ne pouvez répéter qu'une seule fois . Les générateurs ne stockent pas toutes les valeurs en mémoire, ils génèrent les valeurs à la volée :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
C'est la même chose, sauf que vous avez utilisé à la ()
place de []
. MAIS, vous ne pouvez pas effectuer for i in mygenerator
une deuxième fois car les générateurs ne peuvent être utilisés qu'une seule fois: ils calculent 0, puis l'oublient et calculent 1, et terminent le calcul 4, un par un.
rendement
yield
est un mot-clé utilisé comme return
, sauf que la fonction retournera un générateur.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Ici, c'est un exemple inutile, mais c'est pratique lorsque vous savez que votre fonction renverra un énorme ensemble de valeurs que vous n'aurez besoin de lire qu'une seule fois.
Pour maîtriser yield
, vous devez comprendre que lorsque vous appelez la fonction, le code que vous avez écrit dans le corps de la fonction ne s'exécute pas. La fonction ne renvoie que l'objet générateur, c'est un peu délicat :-)
Ensuite, votre code continuera là où il s'était arrêté à chaque for
utilisation du générateur.
Maintenant, la partie difficile:
La première fois que l' for
appel à l'objet générateur créé à partir de votre fonction, il exécutera le code dans votre fonction depuis le début jusqu'à ce qu'il frappe yield
, puis il renverra la première valeur de la boucle. Ensuite, chaque appel suivant exécutera une autre itération de la boucle que vous avez écrite dans la fonction et renverra la valeur suivante. Cela continuera jusqu'à ce que le générateur soit considéré comme vide, ce qui se produit lorsque la fonction s'exécute sans frapper yield
. Cela peut être dû au fait que la boucle est terminée ou parce que vous ne remplissez plus un "if/else"
.
Votre code expliqué
Générateur:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Votre interlocuteur:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Ce code contient plusieurs parties intelligentes:
La boucle itère sur une liste, mais la liste se développe pendant que la boucle est itérée :-) C'est un moyen concis de parcourir toutes ces données imbriquées même si c'est un peu dangereux car vous pouvez vous retrouver avec une boucle infinie. Dans ce cas, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
épuisez toutes les valeurs du générateur, mais while
continue de créer de nouveaux objets générateurs qui produiront des valeurs différentes des précédentes car elles ne sont pas appliquées sur le même nœud.
La extend()
méthode est une méthode d'objet de liste qui attend un itérable et ajoute ses valeurs à la liste.
Habituellement, nous lui passons une liste:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Mais dans votre code, il obtient un générateur, ce qui est bien car:
- Vous n'avez pas besoin de lire les valeurs deux fois.
- Vous pouvez avoir beaucoup d'enfants et vous ne voulez pas qu'ils soient tous stockés en mémoire.
Et cela fonctionne parce que Python ne se soucie pas si l'argument d'une méthode est une liste ou non. Python attend des itérables donc il fonctionnera avec des chaînes, des listes, des tuples et des générateurs! C'est ce qu'on appelle la frappe de canard et c'est l'une des raisons pour lesquelles Python est si cool. Mais ceci est une autre histoire, pour une autre question ...
Vous pouvez vous arrêter ici ou lire un peu pour voir une utilisation avancée d'un générateur:
Contrôler l'épuisement d'un générateur
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Remarque: Pour Python 3, utilisez print(corner_street_atm.__next__())
ouprint(next(corner_street_atm))
Il peut être utile pour diverses choses comme le contrôle de l'accès à une ressource.
Itertools, votre meilleur ami
Le module itertools contient des fonctions spéciales pour manipuler les itérables. Vous avez toujours voulu dupliquer un générateur? Chaîne de deux générateurs? Regrouper les valeurs dans une liste imbriquée avec une ligne? Map / Zip
sans créer une autre liste?
Alors juste import itertools
.
Un exemple? Voyons les éventuels ordres d'arrivée pour une course à quatre chevaux:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Comprendre les mécanismes internes de l'itération
L'itération est un processus impliquant des itérables (implémentation de la __iter__()
méthode) et des itérateurs (implémentation de la __next__()
méthode). Les itérables sont tous les objets dont vous pouvez obtenir un itérateur. Les itérateurs sont des objets qui vous permettent d'itérer sur les itérables.
Il y a plus à ce sujet dans cet article sur le fonctionnement des for
boucles .
yield
n'est pas aussi magique que cette réponse le suggère. Lorsque vous appelez une fonction qui contient uneyield
instruction n'importe où, vous obtenez un objet générateur, mais aucun code ne s'exécute. Ensuite, chaque fois que vous extrayez un objet du générateur, Python exécute du code dans la fonction jusqu'à ce qu'il arrive à uneyield
instruction, puis met en pause et délivre l'objet. Lorsque vous extrayez un autre objet, Python reprend juste après leyield
et continue jusqu'à ce qu'il atteigne un autreyield
(souvent le même, mais une itération plus tard). Cela continue jusqu'à la fin de la fonction, moment auquel le générateur est réputé épuisé.()
place de[]
, en particulier ce qui()
est (il peut y avoir de la confusion avec un tuple).return
instruction. (return
est autorisé dans une fonction contenantyield
, à condition de ne pas spécifier de valeur de retour.)Raccourci vers la compréhension
yield
Lorsque vous voyez une fonction avec des
yield
instructions, appliquez cette astuce simple pour comprendre ce qui se passera:result = []
au début de la fonction.yield expr
parresult.append(expr)
.return result
au bas de la fonction.yield
déclarations! Lisez et comprenez le code.Cette astuce peut vous donner une idée de la logique derrière la fonction, mais ce qui se passe réellement avec
yield
est considérablement différent de ce qui se passe dans l'approche basée sur une liste. Dans de nombreux cas, l'approche du rendement sera beaucoup plus efficace en mémoire et plus rapide. Dans d'autres cas, cette astuce vous coincera dans une boucle infinie, même si la fonction d'origine fonctionne très bien. Continuez à lire pour en savoir plus...Ne confondez pas vos Iterables, itérateurs et générateurs
Tout d'abord, le protocole de l' itérateur - lorsque vous écrivez
Python effectue les deux étapes suivantes:
Obtient un itérateur pour
mylist
:Appel
iter(mylist)
-> cela renvoie un objet avec unenext()
méthode (ou__next__()
en Python 3).[C'est l'étape que la plupart des gens oublient de vous parler]
Utilise l'itérateur pour parcourir les éléments:
Continuez d'appeler la
next()
méthode sur l'itérateur renvoyé par l'étape 1. La valeur de retour denext()
est affectée àx
et le corps de la boucle est exécuté. Si une exceptionStopIteration
est levée de l'intérieurnext()
, cela signifie qu'il n'y a plus de valeurs dans l'itérateur et que la boucle est fermée.La vérité est que Python exécute les deux étapes ci-dessus chaque fois qu'il veut faire une boucle sur le contenu d'un objet - il pourrait donc s'agir d'une boucle for, mais cela pourrait également être du code
otherlist.extend(mylist)
(oùotherlist
est une liste Python).Voici
mylist
un itérable car il implémente le protocole itérateur. Dans une classe définie par l'utilisateur, vous pouvez implémenter la__iter__()
méthode pour rendre les instances de votre classe itérables. Cette méthode doit renvoyer un itérateur . Un itérateur est un objet avec unenext()
méthode. Il est possible d'implémenter les deux__iter__()
etnext()
sur la même classe, et d'avoir un__iter__()
retourself
. Cela fonctionnera pour les cas simples, mais pas lorsque vous souhaitez que deux itérateurs bouclent sur le même objet en même temps.Voilà donc le protocole itérateur, de nombreux objets implémentent ce protocole:
__iter__()
.Notez qu'une
for
boucle ne sait pas de quel type d'objet il s'agit - elle suit simplement le protocole de l'itérateur, et est heureuse d'obtenir élément après élément lors de son appelnext()
. Les listes intégrées renvoient leurs éléments un par un, les dictionnaires renvoient les clés une par une, les fichiers renvoient les lignes une par une, etc. Et les générateurs reviennent ... eh bien, c'est làyield
qu'intervient:Au lieu d'
yield
instructions, si vous aviez troisreturn
instructions,f123()
seule la première serait exécutée et la fonction se fermerait. Mais cef123()
n'est pas une fonction ordinaire. Quandf123()
est appelé, il ne renvoie aucune des valeurs dans les déclarations de rendement! Il renvoie un objet générateur. De plus, la fonction ne sort pas vraiment - elle entre dans un état suspendu. Lorsque lafor
boucle essaie de faire une boucle sur l'objet générateur, la fonction reprend à partir de son état suspendu à la ligne suivante après leyield
retour précédent, exécute la ligne de code suivante, dans ce cas, uneyield
instruction, et la renvoie en tant que prochaine article. Cela se produit jusqu'à ce que la fonction se termine, à quel point le générateur monteStopIteration
, et la boucle se termine.Ainsi, l'objet générateur est un peu comme un adaptateur - à une extrémité, il présente le protocole itérateur, en exposant
__iter__()
et ennext()
méthodes pour garder lafor
boucle heureuse. À l'autre extrémité, cependant, il exécute la fonction juste assez pour en extraire la valeur suivante et la remet en mode suspendu.Pourquoi utiliser des générateurs?
Habituellement, vous pouvez écrire du code qui n'utilise pas de générateurs mais implémente la même logique. Une option consiste à utiliser la «liste» de liste temporaire que j'ai mentionnée précédemment. Cela ne fonctionnera pas dans tous les cas, par exemple si vous avez des boucles infinies, ou cela peut faire un usage inefficace de la mémoire lorsque vous avez une liste vraiment longue. L'autre approche consiste à implémenter une nouvelle classe itérable SomethingIter qui conserve l'état dans les membres d'instance et effectue la prochaine étape logique dans sa méthode
next()
(ou__next__()
en Python 3). Selon la logique, le code à l'intérieur de lanext()
méthode peut sembler très complexe et être sujet à des bogues. Ici, les générateurs offrent une solution propre et facile.la source
send
intégrer un générateur, ce qui représente une grande partie de l'intérêt des générateurs?otherlist.extend(mylist)
" -> C'est incorrect.extend()
modifie la liste sur place et ne renvoie pas d'itérable. Essayer de bouclerotherlist.extend(mylist)
échouera avec unTypeError
car ilextend()
revient implicitementNone
, et vous ne pouvez pas bouclerNone
.mylist
(et nonotherlist
lors de l'exécution)otherlist.extend(mylist)
.Pense-y de cette façon:
Un itérateur est juste un terme de fantaisie pour un objet qui a une
next()
méthode. Donc, une fonction cédée finit par ressembler à ceci:Version originale:
C'est essentiellement ce que fait l'interpréteur Python avec le code ci-dessus:
Pour plus d'informations sur ce qui se passe dans les coulisses, la
for
boucle peut être réécrite comme suit:Est-ce que cela a plus de sens ou vous embrouille plus? :)
Je dois noter qu'il s'agit d' une simplification à des fins d'illustration. :)
la source
__getitem__
pourrait être défini au lieu de__iter__
. Par exempleclass it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
iterator = some_function()
, la variableiterator
n'a plus de fonction appeléenext()
, mais seulement une__next__()
fonction. Je pensais que je le mentionnerais.for
implémentation de boucle que vous avez écrite appelle la__iter__
méthodeiterator
, l'instance instanciée deit
?Le
yield
mot-clé est réduit à deux faits simples:yield
mot clé n'importe où dans une fonction, cette fonction ne retourne plus via l'return
instruction. Au lieu de cela , il renvoie immédiatement un objet "liste en attente" paresseux appelé générateurlist
ouset
ourange
ou dict, avec un protocole intégré pour visiter chaque élément dans un certain ordre .En bref: un générateur est une liste paresseuse en attente incrémentielle , et les
yield
instructions vous permettent d'utiliser la notation de fonction pour programmer les valeurs de liste que le générateur doit cracher de manière incrémentielle.Exemple
Définissons une fonction
makeRange
qui ressemble à celle de Pythonrange
. AppelermakeRange(n)
RETOURNE UN GÉNÉRATEUR:Pour forcer le générateur à renvoyer immédiatement ses valeurs en attente, vous pouvez le passer dans
list()
(comme vous pouvez le faire):Exemple de comparaison avec "renvoyer simplement une liste"
L'exemple ci-dessus peut être considéré comme créant simplement une liste que vous ajoutez et renvoyez:
Il existe cependant une différence majeure; voir la dernière section.
Comment vous pourriez utiliser des générateurs
Un itérable est la dernière partie d'une compréhension de liste, et tous les générateurs sont itérables, ils sont donc souvent utilisés comme ceci:
Pour avoir une meilleure idée des générateurs, vous pouvez jouer avec le
itertools
module (assurez-vous d'utiliserchain.from_iterable
plutôt quechain
lorsque cela est justifié). Par exemple, vous pouvez même utiliser des générateurs pour implémenter des listes paresseuses infiniment longues commeitertools.count()
. Vous pouvez implémenter le vôtredef enumerate(iterable): zip(count(), iterable)
ou le faire avec leyield
mot clé dans une boucle while.Veuillez noter: les générateurs peuvent en fait être utilisés pour bien d'autres choses, telles que la mise en œuvre de coroutines ou de programmation non déterministe ou d'autres choses élégantes. Cependant, le point de vue "listes paresseuses" que je présente ici est l'utilisation la plus courante que vous trouverez.
Dans les coulisses
C'est ainsi que fonctionne le "protocole d'itération Python". C'est-à-dire ce qui se passe quand vous le faites
list(makeRange(5))
. C'est ce que je décris plus tôt comme une "liste incrémentielle paresseuse".La fonction intégrée
next()
appelle simplement la.next()
fonction objets , qui fait partie du "protocole d'itération" et se trouve sur tous les itérateurs. Vous pouvez utiliser manuellement lanext()
fonction (et d'autres parties du protocole d'itération) pour implémenter des choses fantaisistes, généralement au détriment de la lisibilité, alors essayez d'éviter de le faire ...Menus détails
Normalement, la plupart des gens ne se soucieraient pas des distinctions suivantes et voudraient probablement arrêter de lire ici.
En langage Python, un itérable est tout objet qui "comprend le concept d'une boucle for" comme une liste
[1,2,3]
, et un itérateur est une instance spécifique de la boucle for demandée[1,2,3].__iter__()
. Un générateur est exactement le même que n'importe quel itérateur, à l'exception de la façon dont il a été écrit (avec la syntaxe de fonction).Lorsque vous demandez un itérateur à partir d'une liste, il crée un nouvel itérateur. Cependant, lorsque vous demandez un itérateur à un itérateur (ce que vous feriez rarement), il vous donne simplement une copie de lui-même.
Ainsi, dans le cas peu probable où vous omettez de faire quelque chose comme ça ...
... alors souvenez-vous qu'un générateur est un itérateur ; c'est-à-dire qu'il s'agit d'une utilisation unique. Si vous souhaitez le réutiliser, vous devez rappeler
myRange(...)
. Si vous devez utiliser le résultat deux fois, convertissez le résultat en liste et stockez-le dans une variablex = list(myRange(5))
. Ceux qui ont absolument besoin de cloner un générateur (par exemple, qui effectuent une métaprogrammation terrifiante et piratée) peuvent utiliseritertools.tee
si cela est absolument nécessaire, car la proposition de normes Python PEP de l' itérateur copiable a été différée.la source
Aperçu / Résumé de la réponse
yield
, lorsqu'elle est appelée, renvoie un générateur .yield from
.return
dans un générateur.)Générateurs:
yield
n'est légal qu'à l'intérieur d'une définition de fonction, et l'inclusion deyield
dans une définition de fonction fait qu'il retourne un générateur.L'idée de générateurs vient d'autres langages (voir référence 1) avec différentes implémentations. Dans les générateurs de Python, l'exécution du code est gelée au point du rendement. Lorsque le générateur est appelé (les méthodes sont décrites ci-dessous), l'exécution reprend, puis se bloque au rendement suivant.
yield
fournit un moyen facile d' implémenter le protocole itérateur , défini par les deux méthodes suivantes:__iter__
etnext
(Python 2) ou__next__
(Python 3). Ces deux méthodes font d'un objet un itérateur que vous pouvez vérifier avec laIterator
classe de base abstraite ducollections
module.Le type de générateur est un sous-type d'itérateur:
Et si nécessaire, nous pouvons effectuer une vérification de type comme ceci:
Une caractéristique d'un
Iterator
est qu'une fois épuisé , vous ne pouvez pas le réutiliser ou le réinitialiser:Vous devrez en créer un autre si vous souhaitez utiliser à nouveau ses fonctionnalités (voir référence 2):
On peut produire des données par programme, par exemple:
Le générateur simple ci-dessus est également équivalent à celui ci-dessous - à partir de Python 3.3 (et non disponible dans Python 2), vous pouvez utiliser
yield from
:Cependant,
yield from
permet également la délégation aux sous-générateurs, ce qui sera expliqué dans la section suivante sur la délégation coopérative avec les sous-coroutines.Coroutines:
yield
forme une expression qui permet d'envoyer des données dans le générateur (voir référence 3)Voici un exemple, notez la
received
variable, qui pointera vers les données envoyées au générateur:Tout d' abord, il faut faire la queue du générateur avec la fonction builtin,
next
. Il appellera la méthode appropriéenext
ou__next__
, selon la version de Python que vous utilisez:Et maintenant, nous pouvons envoyer des données dans le générateur. (L' envoi
None
est identique à l'appelnext
.):Délégation coopérative à Sub-Coroutine avec
yield from
Maintenant, rappelez-vous que
yield from
c'est disponible dans Python 3. Cela nous permet de déléguer des coroutines à un sous-programme:Et maintenant, nous pouvons déléguer des fonctionnalités à un sous-générateur et il peut être utilisé par un générateur comme ci-dessus:
Vous pouvez en savoir plus sur la sémantique précise du
yield from
au PEP 380.Autres méthodes: fermer et lancer
La
close
méthode déclencheGeneratorExit
au moment où l'exécution de la fonction a été gelée. Cela sera également appelé par__del__
afin que vous puissiez mettre n'importe quel code de nettoyage où vous gérezGeneratorExit
:Vous pouvez également lever une exception qui peut être gérée dans le générateur ou renvoyée à l'utilisateur:
Conclusion
Je pense avoir couvert tous les aspects de la question suivante:
Il s'avère que cela
yield
fait beaucoup. Je suis sûr que je pourrais ajouter des exemples encore plus approfondis à cela. Si vous en voulez plus ou si vous avez des critiques constructives, faites-le moi savoir en commentant ci-dessous.Annexe:
Critique de la réponse du haut / acceptée **
__iter__
méthode renvoyant un itérateur . Un itérateur fournit une méthode.next
(Python 2 ou.__next__
(Python 3), qui est implicitement appelée par desfor
boucles jusqu'à ce qu'elle augmenteStopIteration
, et une fois qu'elle le fait, elle continuera de le faire.yield
partie..next
méthode, quand au contraire , il doit utiliser la fonction builtin,next
. Ce serait une couche d'indirection appropriée, car son code ne fonctionne pas en Python 3.yield
tout.yield
fournies avec les nouvelles fonctionnalitésyield from
de Python 3. La réponse supérieure / acceptée est une réponse très incomplète.Critique de la réponse suggérant
yield
dans un générateur l'expression ou la compréhension.La grammaire permet actuellement toute expression dans une liste de compréhension.
Étant donné que le rendement est une expression, certains ont fait valoir qu'il était intéressant de l'utiliser dans des compréhensions ou des expressions génératrices - en dépit de ne citer aucun cas d'utilisation particulièrement bon.
Les développeurs principaux de CPython discutent de la dépréciation de son allocation . Voici un article pertinent de la liste de diffusion:
De plus, il y a un problème en suspens (10544) qui semble pointer dans le sens que ce n'est jamais une bonne idée (PyPy, une implémentation Python écrite en Python, déclenche déjà des avertissements de syntaxe.)
En résumé, jusqu'à ce que les développeurs de CPython nous disent le contraire: ne mettez pas d'
yield
expression ou de compréhension de générateur.L'
return
instruction dans un générateurEn Python 2 :
An
expression_list
est essentiellement un nombre d'expressions séparées par des virgules - essentiellement, en Python 2, vous pouvez arrêter le générateur avecreturn
, mais vous ne pouvez pas retourner de valeur.En Python 3 :
Notes de bas de page
Les langages CLU, Sather et Icon ont été référencés dans la proposition visant à introduire le concept de générateurs dans Python. L'idée générale est qu'une fonction peut maintenir l'état interne et générer des points de données intermédiaires à la demande de l'utilisateur. Cela promettait d'être supérieur en performances à d'autres approches, y compris le threading Python , qui n'est même pas disponible sur certains systèmes.
Cela signifie, par exemple, que les
xrange
objets (range
en Python 3) ne sont pas desIterator
s, même s'ils sont itérables, car ils peuvent être réutilisés. Comme les listes, leurs__iter__
méthodes renvoient des objets itérateur.yield
a été initialement présenté comme une instruction, ce qui signifie qu'il ne pouvait apparaître qu'au début d'une ligne dans un bloc de code. Crée maintenantyield
une expression de rendement. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Cette modification a été proposée pour permettre à un utilisateur d'envoyer des données dans le générateur comme on pourrait les recevoir. Pour envoyer des données, il faut être en mesure de les affecter à quelque chose, et pour cela, une déclaration ne fonctionnera tout simplement pas.la source
yield
est commereturn
- il renvoie tout ce que vous lui dites (en tant que générateur). La différence est que la prochaine fois que vous appelez le générateur, l'exécution commence à partir du dernier appel à l'yield
instruction. Contrairement au retour, le cadre de pile n'est pas nettoyé lorsqu'un rendement se produit, mais le contrôle est retransféré à l'appelant, donc son état reprendra la prochaine fois que la fonction sera appelée.Dans le cas de votre code, la fonction
get_child_candidates
agit comme un itérateur de sorte que lorsque vous étendez votre liste, elle ajoute un élément à la fois à la nouvelle liste.list.extend
appelle un itérateur jusqu'à épuisement. Dans le cas de l'exemple de code que vous avez publié, il serait beaucoup plus clair de simplement retourner un tuple et de l'ajouter à la liste.la source
Il y a une chose supplémentaire à mentionner: une fonction qui donne n'a pas à se terminer. J'ai écrit du code comme ceci:
Ensuite, je peux l'utiliser dans un autre code comme celui-ci:
Cela aide vraiment à simplifier certains problèmes et facilite le travail avec certaines choses.
la source
Pour ceux qui préfèrent un exemple de travail minimal, méditez sur cette session Python interactive:
la source
TL; DR
Au lieu de cela:
faites ceci:
Chaque fois que vous vous retrouvez à construire une liste à partir de zéro,
yield
chaque pièce à la place.C'était mon premier moment "aha" avec un rendement.
yield
est une façon sucrée de direMême comportement:
Comportement différent:
Le rendement est en un seul passage : vous ne pouvez effectuer une itération qu'une seule fois. Lorsqu'une fonction a un rendement, nous l'appelons une fonction de générateur . Et un itérateur est ce qu'il retourne. Ces termes sont révélateurs. Nous perdons la commodité d'un conteneur, mais gagnons la puissance d'une série calculée selon les besoins et arbitrairement longue.
Le rendement est paresseux , il retarde le calcul. Une fonction avec un rendement ne s'exécute pas du tout lorsque vous l'appelez. Il renvoie un objet itérateur qui se souvient de l'endroit où il s'était arrêté. Chaque fois que vous appelez
next()
l'itérateur (cela se produit dans une boucle for), l'exécution se fait quelques centimètres vers le rendement suivant.return
lève StopIteration et termine la série (c'est la fin naturelle d'une boucle for).Le rendement est polyvalent . Les données ne doivent pas être stockées toutes ensemble, elles peuvent être mises à disposition une par une. Cela peut être infini.
Si vous avez besoin de plusieurs passes et que la série n'est pas trop longue, appelez-la simplement
list()
:Choix brillant du mot
yield
car les deux sens s'appliquent:... fournir les prochaines données de la série.
... abandonner l'exécution du processeur jusqu'à ce que l'itérateur avance.
la source
Le rendement vous donne un générateur.
Comme vous pouvez le voir, dans le premier cas,
foo
conserve la liste entière en mémoire à la fois. Ce n'est pas grave pour une liste à 5 éléments, mais que faire si vous voulez une liste de 5 millions? Non seulement c'est un énorme mangeur de mémoire, mais cela coûte aussi beaucoup de temps à construire au moment où la fonction est appelée.Dans le deuxième cas,
bar
vous donne juste un générateur. Un générateur est un itérable - ce qui signifie que vous pouvez l'utiliser dans unefor
boucle, etc., mais chaque valeur n'est accessible qu'une seule fois. Toutes les valeurs ne sont pas non plus stockées en mémoire en même temps; l'objet générateur "se souvient" de l'endroit où il se trouvait dans la boucle la dernière fois que vous l'avez appelé - de cette façon, si vous utilisez un itérable pour (disons) compter jusqu'à 50 milliards, vous n'avez pas à compter jusqu'à 50 milliards tous à la fois et stocker les 50 milliards de chiffres à compter.Encore une fois, c'est un exemple assez artificiel, vous utiliseriez probablement itertools si vous vouliez vraiment compter jusqu'à 50 milliards. :)
Il s'agit du cas d'utilisation le plus simple des générateurs. Comme vous l'avez dit, il peut être utilisé pour écrire des permutations efficaces, en utilisant yield pour pousser les choses à travers la pile d'appels au lieu d'utiliser une sorte de variable de pile. Les générateurs peuvent également être utilisés pour une traversée d'arbre spécialisée et toutes sortes d'autres choses.
la source
range
retourne également un générateur au lieu d'une liste, donc vous verriez également une idée similaire, sauf que__repr__
/__str__
est remplacé pour afficher un résultat plus agréable, dans ce casrange(1, 10, 2)
.C'est retourner un générateur. Je ne suis pas particulièrement familier avec Python, mais je pense que c'est le même genre de chose que les blocs d'itérateur de C # si vous les connaissez.
L'idée clé est que le compilateur / interprète / quoi que ce soit fait quelque truc pour que, en ce qui concerne l'appelant, ils puissent continuer à appeler next () et il continuera à renvoyer des valeurs - comme si la méthode du générateur était en pause . Maintenant, évidemment, vous ne pouvez pas vraiment "suspendre" une méthode, donc le compilateur construit une machine d'état pour que vous vous souveniez où vous êtes actuellement et à quoi ressemblent les variables locales, etc. C'est beaucoup plus facile que d'écrire un itérateur vous-même.
la source
Il n'y a qu'un seul type de réponse que je ne pense pas avoir encore donné, parmi les nombreuses bonnes réponses qui décrivent comment utiliser les générateurs. Voici la réponse de la théorie du langage de programmation:
L'
yield
instruction en Python renvoie un générateur. Un générateur en Python est une fonction qui renvoie des continuations (et spécifiquement un type de coroutine, mais les continuations représentent le mécanisme le plus général pour comprendre ce qui se passe).Les continuations dans la théorie des langages de programmation sont un type de calcul beaucoup plus fondamental, mais elles ne sont pas souvent utilisées, car elles sont extrêmement difficiles à raisonner et également très difficiles à implémenter. Mais l'idée de ce qu'est une continuation est simple: c'est l'état d'un calcul qui n'est pas encore terminé. Dans cet état, les valeurs actuelles des variables, les opérations qui n'ont pas encore été effectuées, etc., sont enregistrées. Ensuite, à un certain moment plus tard dans le programme, la suite peut être invoquée, de sorte que les variables du programme sont réinitialisées à cet état et les opérations qui ont été enregistrées sont effectuées.
Les suites, sous cette forme plus générale, peuvent être mises en œuvre de deux manières. De la même
call/cc
manière, la pile du programme est littéralement sauvegardée puis lorsque la suite est invoquée, la pile est restaurée.Dans le style de passage de continuation (CPS), les continuations ne sont que des fonctions normales (uniquement dans les langages où les fonctions sont de première classe) que le programmeur gère explicitement et transmet aux sous-programmes. Dans ce style, l'état du programme est représenté par des fermetures (et les variables qui y sont codées) plutôt que par des variables qui résident quelque part sur la pile. Les fonctions qui gèrent le flux de contrôle acceptent la continuation comme arguments (dans certaines variantes de CPS, les fonctions peuvent accepter plusieurs continuations) et manipulent le flux de contrôle en les appelant simplement en les appelant et en revenant par la suite. Un exemple très simple de style de passage de continuation est le suivant:
Dans cet exemple (très simpliste), le programmeur enregistre l'opération d'écriture effective du fichier dans une continuation (qui peut potentiellement être une opération très complexe avec de nombreux détails à écrire), puis passe cette continuation (c.-à-d. fermeture de classe) à un autre opérateur qui effectue un traitement supplémentaire, puis l'appelle si nécessaire. (J'utilise beaucoup ce modèle de conception dans la programmation GUI réelle, soit parce qu'il me permet d'économiser des lignes de code ou, plus important encore, pour gérer le flux de contrôle après le déclenchement des événements GUI.)
Le reste de cet article va, sans perte de généralité, conceptualiser les suites comme CPS, car il est beaucoup plus facile à comprendre et à lire.
Parlons maintenant des générateurs en Python. Les générateurs sont un sous-type spécifique de continuation. Alors que les continuations sont généralement capables de sauvegarder l'état d'un calcul (c'est-à-dire la pile d'appels du programme), les générateurs ne peuvent sauvegarder l'état de l'itération que sur un itérateur . Bien que cette définition soit légèrement trompeuse pour certains cas d'utilisation de générateurs. Par exemple:
Il s'agit clairement d'un itérable raisonnable dont le comportement est bien défini - chaque fois que le générateur l'itère, il renvoie 4 (et le fait pour toujours). Mais ce n'est probablement pas le type prototypique d'itérable qui vient à l'esprit lorsque l'on pense aux itérateurs (c.-à
for x in collection: do_something(x)
-d.). Cet exemple illustre la puissance des générateurs: si quelque chose est un itérateur, un générateur peut sauvegarder l'état de son itération.Pour réitérer: Les continuations peuvent enregistrer l'état de la pile d'un programme et les générateurs peuvent enregistrer l'état d'itération. Cela signifie que les continuations sont beaucoup plus puissantes que les générateurs, mais aussi que les générateurs sont beaucoup, beaucoup plus faciles. Ils sont plus faciles à implémenter pour le concepteur de langage et plus faciles à utiliser pour le programmeur (si vous avez du temps à graver, essayez de lire et de comprendre cette page sur les suites et appelez / cc ).
Mais vous pouvez facilement implémenter (et conceptualiser) des générateurs comme un cas simple et spécifique de style de passage de continuation:
Chaque fois qu'il
yield
est appelé, il indique à la fonction de renvoyer une continuation. Lorsque la fonction est appelée à nouveau, elle démarre là où elle s'est arrêtée. Ainsi, en pseudo-pseudocode (c'est-à-dire pas en pseudocode, mais pas en code), lanext
méthode du générateur est essentiellement la suivante:où le
yield
mot-clé est en fait du sucre syntaxique pour la fonction de générateur réel, essentiellement quelque chose comme:N'oubliez pas que ce n'est qu'un pseudocode et que l'implémentation réelle des générateurs en Python est plus complexe. Mais comme exercice pour comprendre ce qui se passe, essayez d'utiliser le style de passage de continuation pour implémenter des objets générateurs sans utiliser le
yield
mot - clé.la source
Voici un exemple en langage simple. Je fournirai une correspondance entre les concepts humains de haut niveau et les concepts Python de bas niveau.
Je veux opérer sur une séquence de nombres, mais je ne veux pas m'embêter avec la création de cette séquence, je veux seulement me concentrer sur l'opération que je veux faire. Donc, je fais ce qui suit:
Cette étape correspond à l'
def
entrée de la fonction générateur, c'est-à-dire la fonction contenant ayield
.Cette étape correspond à l'appel de la fonction générateur qui renvoie un objet générateur. Notez que vous ne me donnez pas encore de chiffres; vous prenez juste votre papier et votre crayon.
Cette étape correspond à l'appel
.next()
à l'objet générateur.Cette étape correspond à l'objet générateur terminant son travail et levant une
StopIteration
exception La fonction générateur n'a pas besoin de lever l'exception. Il est déclenché automatiquement lorsque la fonction se termine ou émet areturn
.C'est ce que fait un générateur (une fonction qui contient a
yield
); il commence à s'exécuter, s'arrête chaque fois qu'il fait unyield
, et lorsqu'on lui demande un.next()
valeur, il continue à partir du dernier point. Il s'intègre parfaitement par conception avec le protocole itérateur de Python, qui décrit comment demander séquentiellement des valeurs.L'utilisateur le plus connu du protocole itérateur est la
for
commande en Python. Donc, chaque fois que vous faites:peu importe qu'il s'agisse d'
sequence
une liste, d'une chaîne, d'un dictionnaire ou d'un objet générateur comme décrit ci-dessus; le résultat est le même: vous lisez les éléments une à une.Notez que la
def
saisie d'une fonction qui contient unyield
mot-clé n'est pas le seul moyen de créer un générateur; c'est juste le moyen le plus simple d'en créer un.Pour des informations plus précises, lisez les types d'itérateur , l' instruction yield et les générateurs dans la documentation Python.
la source
Bien que de nombreuses réponses montrent pourquoi vous utiliseriez un
yield
pour créer un générateur, il existe d'autres utilisations pouryield
. Il est assez facile de faire une coroutine, qui permet le passage d'informations entre deux blocs de code. Je ne répéterai aucun des beaux exemples qui ont déjà été donnés sur l'utilisationyield
pour créer un générateur.Pour aider à comprendre ce que
yield
fait le code suivant, vous pouvez utiliser votre doigt pour tracer le cycle à travers n'importe quel code qui a unyield
. Chaque fois que votre doigt touche layield
, vous devez attendre unnext
ousend
à saisir. Quand unnext
est appelé, vous tracez le code jusqu'à ce que vous frappiez leyield
… le code à droite duyield
est évalué et renvoyé à l'appelant… puis vous attendez. Lorsquenext
est à nouveau appelé, vous effectuez une autre boucle dans le code. Cependant, vous remarquerez que dans une coroutine,yield
peut également être utilisé avec unsend
... qui enverra une valeur de l'appelant dans la fonction de rendement. Si unsend
est donné, alorsyield
reçoit la valeur envoyée et la recrache du côté gauche… puis la trace à travers le code progresse jusqu'à ce que vous frappiez à était appelée).yield
à nouveau (retour de la valeur à la fin, comme sinext
Par exemple:
la source
Il y a une autre
yield
utilisation et signification (depuis Python 3.3):De PEP 380 - Syntaxe de délégation à un sous-générateur :
De plus cela introduira (depuis Python 3.5):
pour éviter que les coroutines ne soient confondues avec un générateur régulier (aujourd'hui
yield
utilisé dans les deux).la source
Toutes les bonnes réponses, mais un peu difficiles pour les débutants.
Je suppose que vous avez appris la
return
déclaration.Par analogie,
return
etyield
sont des jumeaux.return
signifie «retour et arrêt» tandis que «rendement» signifie «retour, mais continue»Exécuter:
Vous voyez, vous n'obtenez qu'un seul numéro plutôt qu'une liste d'entre eux.
return
ne vous permet jamais de l'emporter joyeusement, met en œuvre une seule fois et quittez.Remplacez
return
paryield
:Maintenant, vous gagnez pour obtenir tous les chiffres.
En comparant celui
return
qui s'exécute une fois et s'arrête,yield
exécute les temps que vous avez planifiés. Vous pouvez interpréterreturn
commereturn one of them
, etyield
commereturn all of them
. C'est ce qu'on appelleiterable
.C'est l'essentiel
yield
.La différence entre une
return
sortie de liste et layield
sortie d' objet est:Vous obtiendrez toujours [0, 1, 2] à partir d'un objet de liste, mais vous ne pourrez les récupérer qu'une seule fois dans «la
yield
sortie de l'objet ». Ainsi, il a un nouvelgenerator
objet de nom comme affiché dansOut[11]: <generator object num_list at 0x10327c990>
.En conclusion, comme métaphore pour le dire:
return
etyield
sont des jumeauxlist
etgenerator
sont des jumeauxla source
yield
. Ceci est important, je pense, et devrait être exprimé.Voici quelques exemples Python sur la façon d'implémenter des générateurs comme si Python ne leur fournissait pas de sucre syntaxique:
En tant que générateur Python:
Utilisation de fermetures lexicales au lieu de générateurs
Utilisation de fermetures d'objets au lieu de générateurs (car ClosuresAndObjectsAreEquivalent )
la source
J'allais poster "lire la page 19 de" Python: référence essentielle "de Beazley pour une description rapide des générateurs", mais tant d'autres ont déjà publié de bonnes descriptions.
Notez également que cela
yield
peut être utilisé dans les coroutines comme le double de leur utilisation dans les fonctions de générateur. Bien que ce ne soit pas la même utilisation que votre extrait de code, il(yield)
peut être utilisé comme expression dans une fonction. Lorsqu'un appelant envoie une valeur à la méthode à l'aide de lasend()
méthode, la coroutine s'exécute jusqu'à la prochaine(yield)
instruction soit rencontrée.Les générateurs et coroutines sont un moyen génial de configurer des applications de type flux de données. J'ai pensé qu'il valait la peine de connaître l'autre utilisation de l'
yield
instruction dans les fonctions.la source
Du point de vue de la programmation, les itérateurs sont implémentés comme des thunks .
Pour implémenter des itérateurs, des générateurs et des pools de threads pour une exécution simultanée, etc. en tant que thunks (également appelés fonctions anonymes), on utilise des messages envoyés à un objet de fermeture, qui a un répartiteur, et le répartiteur répond à des "messages".
http://en.wikipedia.org/wiki/Message_passing
" next " est un message envoyé à une fermeture, créé par l' appel " iter ".
Il existe de nombreuses façons d'implémenter ce calcul. J'ai utilisé la mutation, mais il est facile de le faire sans mutation, en renvoyant la valeur actuelle et le prochain rendement.
Voici une démonstration qui utilise la structure de R6RS, mais la sémantique est absolument identique à celle de Python. C'est le même modèle de calcul, et seul un changement de syntaxe est nécessaire pour le réécrire en Python.
la source
Voici un exemple simple:
Production:
Je ne suis pas développeur Python, mais il me semble
yield
la position du flux de programme et la prochaine boucle commencent à partir de la position "yield". Il semble qu'il attend à cette position, et juste avant cela, renvoyant une valeur à l'extérieur, et la prochaine fois continue de fonctionner.Cela semble être une capacité intéressante et agréable: D
la source
Voici une image mentale de ce qui se
yield
passe.J'aime penser à un thread comme ayant une pile (même s'il n'est pas implémenté de cette façon).
Lorsqu'une fonction normale est appelée, elle place ses variables locales sur la pile, effectue certains calculs, puis efface la pile et retourne. Les valeurs de ses variables locales ne sont jamais revues.
Avec une
yield
fonction, lorsque son code commence à s'exécuter (c'est-à-dire après que la fonction a été appelée, renvoyant un objet générateur, dont lanext()
méthode est ensuite invoquée), elle place également ses variables locales sur la pile et calcule pendant un certain temps. Mais ensuite, quand il frappe l'yield
instruction, avant d'effacer sa partie de la pile et de revenir, il prend un instantané de ses variables locales et les stocke dans l'objet générateur. Il écrit également l'endroit où il se trouve actuellement dans son code (c'est-à-dire l'yield
instruction particulière ).C'est donc une sorte de fonction figée à laquelle le générateur est accroché.
Quand
next()
est appelé par la suite, il récupère les effets de la fonction sur la pile et la ré-anime. La fonction continue de calculer d'où elle s'était arrêtée, sans se rendre compte qu'elle venait de passer une éternité en chambre froide.Comparez les exemples suivants:
Lorsque nous appelons la deuxième fonction, elle se comporte très différemment de la première. La
yield
déclaration peut être inaccessible, mais si elle est présente n'importe où, elle change la nature de ce que nous traitons.L'appel
yielderFunction()
n'exécute pas son code, mais crée un générateur à partir du code. (Peut-être que c'est une bonne idée de nommer de telles choses avec leyielder
préfixe pour la lisibilité.)Les champs
gi_code
etgi_frame
sont l'endroit où l'état figé est stocké. En les explorant avecdir(..)
, nous pouvons confirmer que notre modèle mental ci-dessus est crédible.la source
Comme chaque réponse le suggère,
yield
est utilisé pour créer un générateur de séquence. Il est utilisé pour générer dynamiquement une séquence. Par exemple, lors de la lecture d'un fichier ligne par ligne sur un réseau, vous pouvez utiliser layield
fonction comme suit:Vous pouvez l'utiliser dans votre code comme suit:
Gotcha de transfert de contrôle d'exécution
Le contrôle d'exécution sera transféré de getNextLines () vers la
for
boucle lors de l'exécution de yield. Ainsi, chaque fois que getNextLines () est invoquée, l'exécution commence au point où elle a été interrompue la dernière fois.Donc en bref, une fonction avec le code suivant
imprimera
la source
Un exemple simple pour comprendre ce que c'est:
yield
La sortie est:
la source
print(i, end=' ')
? Sinon, je crois que le comportement par défaut mettrait chaque numéro sur une nouvelle ligne(Ma réponse ci-dessous ne parle que du point de vue de l'utilisation du générateur Python, pas de l' implémentation sous - jacente du mécanisme du générateur , ce qui implique quelques astuces de manipulation de pile et de tas.)
Quand
yield
est utilisé au lieu d'unreturn
dans une fonction python, cette fonction est transformée en quelque chose de spécial appelégenerator function
. Cette fonction renverra un objet degenerator
type. Leyield
mot-clé est un drapeau pour notifier au compilateur python de traiter une telle fonction spécialement. Les fonctions normales se termineront une fois qu'une valeur en sera retournée. Mais avec l'aide du compilateur, la fonction de générateur peut être considérée comme pouvant être reprise. Autrement dit, le contexte d'exécution sera restauré et l'exécution continuera à partir de la dernière exécution. Jusqu'à ce que vous appeliez explicitement return, ce qui déclenchera uneStopIteration
exception (qui fait également partie du protocole itérateur), ou atteindra la fin de la fonction. J'ai trouvé beaucoup de référencesgenerator
mais cidufunctional programming perspective
est le plus digeste.(Maintenant, je veux parler de la raison d'être
generator
et de laiterator
base de ma propre compréhension. J'espère que cela peut vous aider à saisir la motivation essentielle de l'itérateur et du générateur. Un tel concept apparaît dans d'autres langages ainsi que C #.)Si je comprends bien, lorsque nous voulons traiter un tas de données, nous stockons généralement les données quelque part, puis les traitons une par une. Mais cette approche naïve est problématique. Si le volume de données est énorme, il est coûteux de les stocker dans leur ensemble au préalable. Donc, au lieu de stocker le
data
lui - même directement, pourquoi ne pas stocker une sorte demetadata
manière indirecte, c'est-à-direthe logic how the data is computed
.Il existe 2 approches pour encapsuler ces métadonnées.
as a class
. C'est ce que l'on appelleiterator
qui implémente le protocole itérateur (c'est-à-dire les méthodes__next__()
et__iter__()
). C'est également le modèle de conception d'itérateur communément observé .as a function
. C'est ce qu'on appellegenerator function
. Mais sous le capot, le retournégenerator object
encoreIS-A
iterator , car il met également en œuvre le protocole itérateur.Quoi qu'il en soit, un itérateur est créé, c'est-à-dire un objet qui peut vous donner les données que vous souhaitez. L'approche OO peut être un peu complexe. Quoi qu'il en soit, celui à utiliser dépend de vous.
la source
En résumé, l'
yield
instruction transforme votre fonction en une fabrique qui produit un objet spécial appelé agenerator
qui enveloppe le corps de votre fonction d'origine. Lorsque legenerator
est itéré, il exécute votre fonction jusqu'à ce qu'il atteigne la suivante,yield
puis suspend l'exécution et évalue la valeur transmise àyield
. Il répète ce processus à chaque itération jusqu'à ce que le chemin d'exécution quitte la fonction. Par exemple,sort simplement
La puissance provient de l'utilisation du générateur avec une boucle qui calcule une séquence, le générateur exécute la boucle en s'arrêtant à chaque fois pour `` produire '' le résultat suivant du calcul, de cette façon, il calcule une liste à la volée, l'avantage étant la mémoire enregistré pour des calculs particulièrement volumineux
Supposons que vous vouliez créer votre propre
range
fonction qui produit une plage de nombres itérable, vous pouvez le faire comme ça,et l'utiliser comme ça;
Mais cela est inefficace car
Heureusement, Guido et son équipe ont été assez généreux pour développer des générateurs afin que nous puissions simplement le faire;
Maintenant, à chaque itération, une fonction sur le générateur appelé
next()
exécute la fonction jusqu'à ce qu'elle atteigne une instruction «yield» dans laquelle elle s'arrête et «renvoie» la valeur ou atteint la fin de la fonction. Dans ce cas, lors du premier appel,next()
s'exécute jusqu'à la déclaration de rendement et donne «n», lors du prochain appel, il exécutera la déclaration d'incrémentation, reviendra au «tout», l'évaluera et, s'il est vrai, il s'arrêtera et renvoie 'n', il continuera de cette façon jusqu'à ce que la condition while renvoie false et que le générateur saute à la fin de la fonction.la source
Le rendement est un objet
Un
return
dans une fonction renverra une seule valeur.Si vous souhaitez qu'une fonction renvoie un énorme ensemble de valeurs , utilisez
yield
.Plus important encore,
yield
est une barrière .Autrement dit, il exécutera le code dans votre fonction depuis le début jusqu'à ce qu'il frappe
yield
. Ensuite, il retournera la première valeur de la boucle.Ensuite, tous les autres appels exécuteront la boucle que vous avez écrite dans la fonction une fois de plus, en retournant la valeur suivante jusqu'à ce qu'il n'y ait aucune valeur à renvoyer.
la source
Beaucoup de gens utilisent
return
plutôt queyield
, mais dans certains cas, ilsyield
peuvent être plus efficaces et plus faciles à travailler.Voici un exemple qui
yield
est certainement le meilleur pour:Les deux fonctions font la même chose, mais
yield
utilisent trois lignes au lieu de cinq et ont une variable de moins à se soucier.Comme vous pouvez le voir, les deux fonctions font la même chose. La seule différence est
return_dates()
donne une liste etyield_dates()
donne un générateur.Un exemple concret serait quelque chose comme lire un fichier ligne par ligne ou si vous voulez simplement créer un générateur.
la source
yield
est comme un élément de retour pour une fonction. La différence est que l'yield
élément transforme une fonction en générateur. Un générateur se comporte comme une fonction jusqu'à ce que quelque chose soit «cédé». Le générateur s'arrête jusqu'à son prochain appel et continue exactement au même point qu'il a commencé. Vous pouvez obtenir une séquence de toutes les valeurs «cédées» en une, en appelantlist(generator())
.la source
Le
yield
mot clé recueille simplement les résultats renvoyés. Pensez àyield
commereturn +=
la source
Voici une
yield
approche basée simple , pour calculer la série de fibonacci, expliquée:Lorsque vous saisissez cela dans votre REPL, puis essayez de l'appeler, vous obtenez un résultat mystifiant:
En effet, la présence de
yield
signalés à Python que vous souhaitez créer un générateur , c'est-à-dire un objet qui génère des valeurs à la demande.Alors, comment générez-vous ces valeurs? Cela peut être fait directement en utilisant la fonction intégrée
next
ou indirectement en l'alimentant dans une construction qui consomme des valeurs.En utilisant la
next()
fonction intégrée, vous appelez directement.next
/__next__
, forçant le générateur à produire une valeur:Indirectement, si vous fournissez
fib
à unefor
boucle, unlist
initialiseur, untuple
initialiseur ou tout autre élément qui attend un objet qui génère / produit des valeurs, vous "consommerez" le générateur jusqu'à ce qu'il ne puisse plus produire de valeurs par lui (et il revient) :De même, avec un
tuple
initialiseur:Un générateur diffère d'une fonction dans le sens où il est paresseux. Il accomplit cela en maintenant son état local et en vous permettant de reprendre quand vous en avez besoin.
Lorsque vous l'
fib
appelez pour la première fois en l'appelant:Python compile la fonction, rencontre le
yield
mot - clé et vous renvoie simplement un objet générateur. Il ne semble pas très utile.Lorsque vous demandez ensuite qu'il génère la première valeur, directement ou indirectement, il exécute toutes les instructions qu'il trouve, jusqu'à ce qu'il rencontre un
yield
, il renvoie ensuite la valeur que vous avez fournieyield
et s'arrête. Pour un exemple qui illustre mieux cela, utilisons quelquesprint
appels (remplacer parprint "text"
if sur Python 2):Maintenant, entrez dans le REPL:
vous avez maintenant un objet générateur en attente d'une commande pour générer une valeur. Utilisez
next
et voyez ce qui est imprimé:Les résultats non cotés sont ce qui est imprimé. Le résultat cité est ce qui est renvoyé
yield
. Appelez ànext
nouveau maintenant:Le générateur se souvient qu'il s'est arrêté
yield value
et reprend à partir de là. Le message suivant est imprimé et la recherche de l'yield
instruction pour y faire une pause a été exécutée à nouveau (en raison de lawhile
boucle).la source