Que fait le mot-clé rendement?

Quelle est l'utilisation du mot-clé de yield en Python? Que fait-il?

Par exemple, j'essaie de comprendre ce code 1 :

 def _get_child_candidates(self, distance, min_dist, max_dist): if self._leftchild and distance - max_dist < self._median: yield self._leftchild if self._rightchild and distance + max_dist >= self._median: yield self._rightchild 

Et ceci est un composeur

 result, candidates = [], [self] while candidates: node = candidates.pop() distance = node._get_dist(obj) if distance <= max_dist and distance >= min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result 

Que se passe-t-il lorsque la méthode _get_child_candidates ? La liste est-elle retournée? Un seul article? Est-il appelé à nouveau? Quand les appels de suivi s'arrêteront-ils?


1. Le code provient de Jochen Schulz (jrschulz), qui a créé une excellente bibliothèque Python pour les espaces métriques. Ceci est un lien vers la source complète: Le module mspace .

8911
donnée par Alex. 24 окт. S. 24 oct. 2008-10-24 01:21 08 à 01h21 2008-10-24 01:21
@ 46 réponses
  • 1
  • 2

Pour comprendre ce qu'est un yield , vous devez comprendre ce que sont les générateurs. Et avant que les générateurs viennent itérateurs.

itéré

Lorsque vous créez une liste, vous pouvez lire ses éléments un à un. La lecture de ses éléments un par un s'appelle une itération:

 >>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3 

mylist est répétable. Lorsque vous utilisez la compréhension de liste, vous créez une liste, qui peut donc être répétée:

 >>> mylist = [x*x for x in range(3)] >>> for i in mylist: ... print(i) 0 1 4 

Tout ce que vous pouvez utiliser " for... in... " est itératif; lists , strings , fichiers ...

Ces itérations sont pratiques car vous pouvez en lire autant que vous le souhaitez, mais vous gardez toutes les valeurs en mémoire et ce n’est pas toujours ce que vous voulez lorsque vous avez beaucoup de valeurs.

Générateurs

Les générateurs sont des itérateurs, une sorte d’itération que vous ne pouvez répéter qu’une fois . Les générateurs ne stockent pas toutes les valeurs en mémoire, ils génèrent des 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é () au lieu de [] . MAIS, vous ne pouvez pas faire for я in mygenerator seconde 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 finissent par calculer 4, l’un après l’autre.

Rendement

yield est un mot clé qui est 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 

Voici un exemple inutile, mais il est utile lorsque vous savez que votre fonction renverra un énorme ensemble de valeurs que vous ne devez lire qu'une fois.

Pour faire face au yield , vous devez comprendre que lorsque vous appelez une fonction, le code écrit dans le corps de la fonction ne commence pas. La fonction ne renvoie que l'objet du générateur, c'est un peu compliqué :-)

Ensuite, votre code continuera d’où il s’est arrêté à chaque fois for utiliser le générateur.

Maintenant la partie la plus difficile:

La première fois que vous appelez for un objet générateur créé à partir de votre fonction sera exécuté, il exécutera le code dans votre fonction du début jusqu'à ce qu'il atteigne le yield , puis retournera la première valeur de la boucle. Ensuite, chaque appel suivant démarrera à nouveau la boucle que vous avez écrite dans la fonction et renverra la valeur suivante jusqu'à ce que la valeur soit renvoyée.

Le générateur est considéré comme vide après le démarrage de la fonction, mais n'entre plus dans le yield . Cela peut être dû au fait que le cycle est terminé ou au fait que vous ne remplissez plus le "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 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 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 

Abonné:

 # 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 candidates 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:

  • Le cycle est répété dans la liste, mais la liste s’allonge lors de l’itération de la boucle :-) C’est un moyen rapide de parcourir toutes ces données imbriquées, même si c’est un peu dangereux, car vous pouvez obtenir une boucle infinie. Dans ce cas, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) épuise toutes les valeurs du générateur, mais continue de créer de nouveaux objets générateurs générant des valeurs autres que les précédentes, car cela ne s'applique pas au même noeud. .

  • La méthode extend() est une méthode d'un objet de liste qui attend l'itération et ajoute ses valeurs à la liste.

Habituellement, nous lui donnons une liste:

 >>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4] 

Mais dans votre code, il y a un générateur, ce qui est bien, parce que:

  1. Vous n'avez pas besoin de lire les valeurs deux fois.
  2. Vous pouvez avoir beaucoup d'enfants et vous ne voulez pas qu'ils soient tous gardés en mémoire.

Et cela fonctionne parce que Python ne se soucie pas de savoir si l'argument de la méthode est une liste ou non. Python attend l'itération, il fonctionnera donc avec des chaînes, des listes, des n-uplets et des générateurs! C'est ce qu'on appelle le 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 l’utilisation avancée du générateur:

Contrôle d'épuisement du générateur

 >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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 ... 

Note Pour Python 3, utilisez print(corner_street_atm.__next__()) ou print(next(corner_street_atm))

Cela peut être utile pour diverses choses, telles que le contrôle de l'accès à une ressource.

Itertools, votre meilleur ami

Le module itertools contient des fonctions spéciales pour la gestion des itérations. Avez-vous déjà voulu dupliquer un générateur? Une 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?

Ensuite, import itertools simplement import itertools .

Un exemple? Jetons un coup d'œil aux procédures d'arrivée possibles pour les courses de 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 d'itération internes

L'itération est un processus qui implique des itérations (implémentation de la __iter__() ) et des itérateurs (implémentation de la __next__() ). Les itérations sont des objets à partir desquels vous pouvez obtenir un itérateur. Les itérateurs sont des objets qui vous permettent de répéter des itérations.

Il y a plus à ce sujet dans cet article sur la façon de travailler en boucle .

13022
24 окт. La réponse est donnée par e-satis le 24 oct. 2008-10-24 01:48 '08 à 1:48 2008-10-24 01:48

Label pour le yield Grocking

Lorsque vous voyez une fonction avec yield , utilisez cette astuce simple pour comprendre ce qui va se passer:

  1. Insérer le result = [] au début de la fonction.
  2. Remplacez chaque yield expr par result.append(expr) .
  3. Insère le résultat de la ligne de return result au bas de la fonction.
  4. Yay - plus de déclarations de yield ! Lire et trouver le code.
  5. Comparez la fonction avec la définition d'origine.

Cette technique peut vous donner une idée de la logique d'une fonction, mais ce qui se produit réellement avec un yield est très différent de ce qui se produit dans l'approche par liste. Dans de nombreux cas, l’approche du rendement sera beaucoup plus efficace et plus rapide. Dans d'autres cas, cette astuce va rester bloquée dans une boucle sans fin, même si la fonction d'origine fonctionne correctement. Lisez la suite pour en savoir plus ...

Ne confondez pas vos itérateurs, itérateurs et générateurs.

Tout d'abord, le protocole itérateur - lorsque vous écrivez

 for x in mylist: ...loop body... 

Python effectue les deux étapes suivantes:

  1. Obtient un itérateur pour mylist :

    Appeler iter(mylist) → renvoie un objet avec la méthode next() (ou __next__() dans Python 3).

    [Ceci est une étape que la plupart des gens oublient de parler]

  2. Utilise un itérateur pour mettre en boucle des éléments:

    Continuez à appeler la méthode next() sur l'itérateur renvoyé à l'étape 1. La valeur renvoyée next() affectée à x et le corps de la boucle est exécuté. Si l'exception StopIteration appelée de next() , cela signifie qu'il n'y a plus de valeurs dans l'itérateur et que le cycle se termine.

La vérité est que Python exécute les deux étapes ci-dessus à tout moment lorsqu'il veut parcourir le contenu d'un objet - il peut donc s'agir d'une boucle for, mais il peut également s'agir d'un code similaire à otherlist.extend(mylist) (où autre otherlist est une liste Python ),

border=0

Ici, mylist est itératif puisqu'il implémente le protocole itérateur. Dans une classe définie par l'utilisateur, vous pouvez implémenter la __iter__() pour rendre vos instances de classe itératives. Cette méthode devrait retourner un itérateur. Un itérateur est un objet avec une méthode next() . Vous pouvez implémenter __iter__() et next() dans la même classe et demander à __iter__() retourner. Cela fonctionnera pour des cas simples, mais pas lorsque vous voulez que deux itérateurs effectuent le cycle du même objet en même temps.

Donc, dans le protocole itérateur, de nombreux objets implémentent ce protocole:

  1. Listes intégrées, dictionnaires, n-uplets, ensembles, fichiers.
  2. Classes personnalisées qui implémentent __iter__() .
  3. Générateurs.

Notez que la boucle for ne sait pas quel objet elle traite - elle ne fait que suivre le protocole itérateur et souhaite obtenir élément par élément lors de l'appel de next() . 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 chaînes une par une, etc. Et les générateurs reviennent ... eh bien, quand le yield vient:

 def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 

Au lieu des yield , s'il y avait trois f123() return f123() dans f123() seul le premier et la fonction f123() . Mais f123() pas une fonction ordinaire. Lorsque f123() , il ne renvoie aucune valeur dans les instructions de rendement! Retourne un objet générateur. De plus, la fonction ne sort pas réellement, elle entre en attente. Lorsque la boucle for tente de mettre en boucle l'objet générateur, la fonction revient de son état de pause à la ligne suivante après le résultat renvoyé précédemment, exécute la ligne de code suivante, dans ce cas la yield , et la renvoie comme élément suivant. Cela se produit jusqu'à ce que la fonction soit relâchée et à ce moment le générateur StopIteration et le cycle StopIteration .

Ainsi, l'objet générateur est similaire à un adaptateur: à une extrémité, il illustre un protocole d'itérateur, en fournissant __iter__() et next() pour maintenir une boucle for en bon état. À l’autre extrémité, cependant, il lance une fonction suffisante pour obtenir la valeur suivante et la remet en mode veille.

Pourquoi utiliser des générateurs?

Vous pouvez généralement écrire du code qui n'utilise pas de générateurs, mais implémente la même logique. Une option consiste à utiliser la liste des astuces temporaires que j'ai mentionnée plus tôt. Cela ne fonctionnera pas dans tous les cas, par exemple si vous avez des boucles infinies, ou cela peut conduire à une utilisation inefficace de la mémoire lorsque vous avez une très longue liste. Une autre approche consiste à implémenter la nouvelle classe itérative SomethingIter qui enregistre l'état dans les éléments de l'instance et effectue la prochaine étape logique via la méthode next() (ou __next__() dans Python 3). Selon la logique, le code dans la méthode next() peut sembler très compliqué et sujet aux erreurs. Ici, les générateurs offrent une solution simple et propre.

1744
26 окт. réponse donnée par l' utilisateur28409 26 octobre 2008-10-26 00:22 '08 à 0:22 2008-10-26 00:22

Pensez-y comme ceci:

Un itérateur n'est qu'un terme sophistiqué désignant un objet doté d'une méthode next (). Donc, la fonction cédée ressemble en fin de compte à ceci:

Version originale:

 def some_function(): for i in xrange(4): yield i for i in some_function(): print i 

C’est essentiellement ce que l’interpréteur Python fait avec le code ci-dessus:

 class it: def __init__(self): # Start at -1 so that we get 0 when we add 1 below. self.count = -1 # The __iter__ method will be called once by the 'for' loop. # The rest of the magic happens on the object returned by this method. # In this case it is the object itself. def __iter__(self): return self # The next method will be called repeatedly by the 'for' loop # until it raises StopIteration. def next(self): self.count += 1 if self.count < 4: return self.count else: # A StopIteration exception is raised # to signal that the iterator is done. # This is caught implicitly by the 'for' loop. raise StopIteration def some_func(): return it() for i in some_func(): print i 

Pour mieux comprendre ce qui se passe en coulisse, la boucle for peut être récrite comme suit:

 iterator = some_func() try: while 1: print iterator.next() except StopIteration: pass 

Cela a-t-il plus de sens ou vous déroute-t-il? :)

Je dois souligner qu'il s'agit d'une simplification à des fins d'illustration. :)

441
24 окт. Répondre à Jason Baker le 24 octobre 2008-10-24 01:28 '08 à 1:28 2008-10-24 01:28

Le mot-clé de yield se résume à deux faits simples:

  1. Si le compilateur détecte le mot-clé de yield n'importe où dans une fonction, cette fonction n'est plus renvoyée via l' return . Au lieu de cela, il retourne immédiatement un objet de liste d'attente paresseux, appelé générateur.
  2. Le générateur est répété. Qu'est-ce qui est répétable? Cela ressemble à une range list ou à une dict-view avec un protocole intégré pour visiter chaque élément dans un ordre spécifique.

En un mot: un générateur est une liste paresseuse, augmentant progressivement , et les yield vous permettent d’utiliser la fonction de notation pour programmer les valeurs de liste que le générateur doit générer progressivement.

 generator = myYieldingFunction(...) x = list(generator) generator v [x[0], ..., ???] generator v [x[0], x[1], ..., ???] generator v [x[0], x[1], x[2], ..., ???] StopIteration exception [x[0], x[1], x[2]] done list==[x[0], x[1], x[2]] 

un exemple

Définissons la fonction makeRange qui est similaire à la range Python. L’ makeRange(n) le makeRange(n) :

 def makeRange(n): # return 0,1,2,...,n-1 i = 0 while i < n: yield i i += 1 >>> makeRange(5) <generator object makeRange at 0x19e4aa0> 

Pour que le générateur retourne immédiatement les valeurs en attente, vous pouvez le passer à list() (comme n'importe quel itérateur):

 >>> list(makeRange(5)) [0, 1, 2, 3, 4] 

Comparer un exemple avec "juste retourner une liste"

L'exemple ci-dessus peut être considéré comme créant simplement une liste à laquelle vous ajoutez et retournez:

 # list-version # # generator-version def makeRange(n): # def makeRange(n): """return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1""" TO_RETURN = [] #> i = 0 # i = 0 while i < n: # while i < n: TO_RETURN += [i] #~ yield i i += 1 # i += 1 ## indented return TO_RETURN #> >>> makeRange(5) [0, 1, 2, 3, 4] 

Cependant, il y a une différence significative. Voir la dernière section.


Comment pouvez-vous utiliser des générateurs

Itéré est la dernière partie de la compréhension de la liste, et tous les générateurs sont itératifs, ils sont donc souvent utilisés comme ceci:

 # _ITERABLE_ >>> [x+10 for x in makeRange(5)] [10, 11, 12, 13, 14] 

Pour mieux comprendre les générateurs, vous pouvez jouer avec le module itertools (assurez-vous d'utiliser chain.from_iterable et non chain avec une garantie pour chain.from_iterable ). Par exemple, vous pouvez même utiliser des générateurs pour implémenter des listes paresseuses infiniment longues, telles que itertools.count() . Vous pouvez implémenter votre propre def enumerate(iterable): zip(count(), iterable) ou alternativement en utilisant le mot-clé yield dans une boucle while.

Notez que les générateurs peuvent être utilisés à de nombreuses autres fins, telles que la mise en œuvre de coroutines, de programmes non déterministes ou autres choses élégantes. Cependant, la vue «listes paresseuses», que je représente ici, est la zone d’utilisation la plus courante que vous trouverez.


Dans les coulisses

Voici comment fonctionne le protocole d'itération Python. C'est ce qui arrive quand vous faites une list(makeRange(5)) . C’est ce que je décris plus tôt comme étant une "liste supplémentaire paresseuse".

 >>> x=iter(range(5)) >>> next(x) 0 >>> next(x) 1 >>> next(x) 2 >>> next(x) 3 >>> next(x) 4 >>> next(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

La fonction .next() appelle simplement les objets .next() , qui fait partie du "protocole d'itération" et se produit sur tous les itérateurs. Vous pouvez utiliser manuellement la fonction next() (et d'autres parties du protocole d'itération) pour implémenter des choses inhabituelles, généralement au détriment de la lisibilité, essayez donc de ne pas le faire ...


petites choses

Habituellement, la plupart des gens ne s’intéressent pas aux différences suivantes et voudront probablement arrêter de lire ici.

En Python, itératif est un objet qui "comprend le concept d'une boucle for", par exemple une liste [1,2,3] , et un itérateur est une instance spécifique de la boucle for demandée, par exemple [1,2,3].__iter__() . Le 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 la fonction).

Lorsque vous demandez un itérateur dans la liste, un nouvel itérateur est créé. Cependant, lorsque vous demandez un itérateur à un itérateur (ce que vous faites rarement), il vous en donne simplement la copie.

Donc, dans le cas improbable où vous ne pourriez pas faire quelque chose comme ça ...

 > x = myRange(5) > list(x) [0, 1, 2, 3, 4] > list(x) [] 

... alors rappelez-vous que le générateur est un itérateur; c'est-à-dire une utilisation ponctuelle. Si vous souhaitez le réutiliser, vous devez myRange(...) appeler myRange(...) . Si vous devez utiliser le résultat deux fois, convertissez-le en liste et enregistrez-le dans la variable x = list(myRange(5)) . Ceux qui ont absolument besoin de cloner un générateur (par exemple, qui effectue une métaprogrammation terriblement pirate) peuvent utiliser itertools.tee si itertools.tee est absolument nécessaire, car l'offre des standards Python PEP pour l' itérateur a été retardée.

378
19 июня '11 в 9:33 2011-06-19 09:33 la réponse est donnée ninjagecko le 19 juin '11 à 9:33 2011-06-19 09:33

Que fait le mot-clé yield en python?

Schéma de réponse / résumé

  • La fonction de yield sur appel renvoie un générateur .
  • Les générateurs sont des itérateurs car ils implémentent un protocole d'itérateur , ce qui vous permet de les parcourir.
  • Des informations peuvent également être envoyées au générateur, ce qui en fait une coroutine .
  • En Python 3, vous pouvez déléguer d'un générateur à un autre dans les deux sens en utilisant le yield from .
  • (L’application critique plusieurs réponses, y compris celle du haut, et discute de l’utilisation de return dans le générateur.)

Générateurs:

yield допустим только внутри определения функции, и включение yield в определение функции заставляет его возвращать генератор.

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В Python Generators выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

yield предоставляет простой способ реализации протокола итератора , который определяется следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба эти метода делают объект итератором, который можно проверить типом с помощью абстрактного базового класса Iterator из модуля collections .

 >>> def func(): ... yield 'I am' ... yield 'a generator!' ... >>> type(func) # A function with yield is still a function <type 'function'> >>> gen = func() >>> type(gen) # but it returns a generator <type 'generator'> >>> hasattr(gen, '__iter__') # that an iterable True >>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3) True # implements the iterator protocol.