Que fait le mot clé" rendement"?

à quoi sert le mot-clé yield en Python? Que faut-il faire?

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 voici l'appelant:

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 est appelée? Est une liste retournée? Un seul élément? Est-il appelé à nouveau? Quand les appels suivants arrêter?


1. Le code vient de Jochen Schulz (jrschulz), qui a fait une grande bibliothèque Python pour les espaces métriques. C'est le lien vers la source complète: Module mspace .

8531
demandé sur linusg 2008-10-24 02:21:11
la source

30 ответов

pour comprendre ce que yield fait, vous devez comprendre ce que générateurs sont. Et avant les générateurs viennent iterables .

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 s'appelle itération:

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

mylist est un itératif . Lorsque vous utilisez une liste comprendre, 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 " for... in... " on est un itérable; lists , strings , fichiers...

ces itérables sont pratiques parce que vous pouvez les lire autant que vous le souhaitez, mais vous stockez toutes les valeurs dans la mémoire et ce n'est pas toujours ce que vous voulez quand vous avez beaucoup de valeurs.

générateurs

générateurs sont itérateurs, une sorte de itérable vous ne pouvez itérer plus d'une 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 exactement la même chose sauf que vous avez utilisé () au lieu de [] . Mais, vous ne peut pas effectuer for i in mygenerator une deuxième fois depuis générateurs peuvent être utilisés qu'une seule fois: ils calculent 0, puis oublier et calculer 1, et finir le calcul 4, un par un.

"1519410920 le" 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

ici, c'est un exemple inutile, mais c'est pratique quand vous savez que votre fonction retournera un énorme ensemble de valeurs que vous n'aurez besoin de lire qu'une seule fois.

maître 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 generator, c'est un peu délicat: -)

Alors, votre code sera exécuté chaque fois que le for utilise le générateur.

Maintenant, le plus dur:

la première fois que le for appelle l'objet générateur créé à partir de votre fonction, il exécutera le code dans votre fonction à partir du en commençant jusqu'à ce qu'il atteigne yield , alors il retournera la première valeur de la boucle. Ensuite, chaque autre appel lancera la boucle que vous avez écrite dans la fonction une fois de plus, et retournera la valeur suivante, jusqu'à ce qu'il n'y ait plus de valeur à retourner.

le générateur est considéré comme vide une fois que la fonction fonctionne, mais ne touche plus yield . Cela peut être parce que la boucle avait pris fin, ou parce que vous ne satisfiez plus un "if/else" .


votre code expliqué

génératrice:

# 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

appelant:

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

  • la boucle itère sur une liste, mais la liste s'étend alors que la boucle est itérée :-) c'est une façon concise de passer en revue 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)) épuise toutes les valeurs du générateur, mais while continue à créer de nouveaux objets du générateur qui produiront des valeurs différentes de celles des précédents puisqu'il n'est pas appliqué sur le même noeud.

  • la méthode extend() est une méthode d'objet de liste qui s'attend à une itération 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 bon parce que:

  1. vous n'avez pas besoin de lire les valeurs deux fois.
  2. Vous pouvez avoir beaucoup d'enfants et que vous ne voulez pas tous stockées dans la 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! Cela s'appelle duck typing et est l'une des raisons pour lesquelles Python est si cool. Mais ceci est une autre histoire, une autre question...

vous pouvez Vous arrêter là, ou de lire un peu pour voir une utilisation avancée d'un générateur:

Contrôle d'une génératrice à l'épuisement

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "0"
>>> 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())
0
>>> print(corner_street_atm.next())
0
>>> print([corner_street_atm.next() for cash in range(5)])
['0', '0', '0', '0', '0']
>>> 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
0
0
0
0
0
0
0
0
0
...

Note: pour Python 3, Utilisez print(corner_street_atm.__next__()) ou print(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. Jamais souhaitez dupliquer un générateur? Chaîne de deux générateurs? Les valeurs de groupe dans une liste imbriquée avec une doublure unique? Map / Zip sans créer une autre liste?

puis juste import itertools .

un exemple? Voyons les ordres d'arrivée possibles pour un cheval de quatre. course:

>>> 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

itération est un processus impliquant itérables (mise en œuvre de la méthode __iter__() ) et itérateurs (mise en œuvre de la méthode __next__() ). Iterables sont tous les objets que vous pouvez obtenir un itérateur. Les itérateurs sont des objets qui vous permettent d'itérer sur des itérables.

Il ya plus à ce sujet dans cet article sur comment for boucles travailler .

12493
répondu e-satis 2018-05-20 12:49:28
la source

raccourci vers Grokking" 1519500920 yield

quand vous voyez une fonction avec les instructions yield , appliquez cette astuce facile pour comprendre ce qui va se passer:

  1. Insérer une ligne result = [] au début de la fonction.
  2. remplacer chaque yield expr par result.append(expr) .
  3. insérer une ligne return result au bas de la fonction.
  4. Yay, plus de yield états! Lire et comprendre le code.
  5. fonction de comparaison à la définition d'origine.

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 significativement différent de ce qui se passe dans l'approche basée sur la liste. Dans de nombreux cas, l'approche de rendement sera beaucoup plus efficace de mémoire et plus rapide aussi. Dans d'autres cas ce tour vous obtiendrez bloqué dans une boucle infinie, même si la fonction d'origine fonctionne très bien. Lisez la suite pour en savoir plus...

Ne confondez pas votre Iterables, les Itérateurs et les Générateurs de

D'abord, le protocole itérateur - quand vous écrivez

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

Python effectue les deux étapes suivantes:

  1. obtient un itérateur pour mylist :

    Appeler iter(mylist) - > ceci renvoie un objet avec une méthode next() (ou __next__() en Python 3).

  2. utilise l'itérateur pour passer en boucle les éléments:

    continuez à appeler la méthode next() sur l'itérateur retourné de l'étape 1. La valeur de retour de next() est assignée à x et le corps de boucle est exécuté. Si un l'exception StopIteration est soulevée de l'intérieur de next() , cela signifie qu'il n'y a plus de valeurs dans l'itérateur et la boucle est sortie.

la vérité est que Python exécute les deux étapes ci - dessus chaque fois qu'il veut boucle sur le contenu d'un objet-donc il pourrait être un pour boucle, mais il pourrait aussi être le code comme otherlist.extend(mylist) (où otherlist est une liste Python).

ici mylist est un iterable parce qu'il implémente le protocole iterator. Dans une classe définie par l'utilisateur, vous pouvez implémenter la méthode __iter__() pour rendre les instances de votre classe itérables. Cette méthode doit retourner un itérateur . Un itérateur est un objet avec une méthode next() . Il est possible de mettre en œuvre à la fois __iter__() et next() sur la même classe, et ont __iter__() retour self . Cela fonctionne pour les cas simples, mais pas quand vous deux itérateurs en boucle sur le même objet en même temps.

donc c'est le protocole iterator, beaucoup d'objets implémentent ce protocole:

  1. listes intégrées, dictionnaires, tuples, sets, files.
  2. classes définies par L'utilisateur qui implémentent __iter__() .
  3. générateurs.

notez qu'une boucle for ne sait pas quel type d'objet il s'agit traitant - il suit l'itérateur protocole, et est heureux d'obtenir l'élément après élément, tel qu'il appelle next() . Les listes intégrées renvoient leurs articles 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... c'est là que yield entre en jeu:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

au lieu de yield déclarations, si vous aviez trois return déclarations dans f123() seul le premier serait exécuté, et la fonction se terminerait. Mais f123() n'est pas une fonction ordinaire. Quand f123() est appelé, il ne renvoie aucune des valeurs dans les déclarations de rendement! Il renvoie un objet générateur. En outre, la fonction ne sort pas vraiment - elle va dans un état suspendu. Lorsque la boucle for essaie de boucler l'objet Générateur, la fonction reprend son état de suspension à la ligne suivante après le yield il a déjà retourné de, exécute la ligne suivante du code, dans ce cas un yield déclaration, et retourne que comme l'article suivant. Cela se produit jusqu'à la sortie de la fonction, à partir de laquelle le Générateur se lève StopIteration , et la boucle se termine.

donc l'objet générateur est un peu comme un adaptateur - à une extrémité il montre le protocole itérateur, en exposant __iter__() et next() méthodes pour garder la boucle for heureux. À l'autre extrémité cependant, il exécute la fonction juste assez pour en retirer 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 temporaire 'trick' 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 il peut faire une utilisation inefficace de la mémoire quand vous avez une liste vraiment longue. Les autres l'approche consiste à implémenter une nouvelle classe itérable SomethingIter qui garde les membres de l'état en instance et effectue la prochaine étape logique dans sa méthode next() (ou __next__() en Python 3). Selon la logique, le code contenu dans la méthode next() peut finir par sembler très complexe et être sujet à des bogues. Ici les générateurs fournissent une solution propre et facile.

1677
répondu user28409 2016-08-16 00:29:29
la source

Pensez-y de cette façon:

un itérateur est juste un terme de son fantaisiste pour un objet qui a une méthode next (). Ainsi, une fonction cédée finit par être quelque chose comme 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 plus d'informations sur ce qui se passe dans les coulisses, la boucle for peut être réécrit à ceci:

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

est-ce que cela a plus de sens ou juste vous embrouiller plus? :)

je dois noter que ce est une simplification excessive à des fins illustratives. :)

411
répondu Jason Baker 2018-05-20 13:02:17
la source

le mot-clé yield est réduit à deux simples faits:

  1. si le compilateur détecte le yield mot-clé anywhere à l'intérieur d'une fonction, cette fonction ne retourne plus via la déclaration return . au lieu de , il immédiatement retourne un paresseux" liste en attente "objet appelé un générateur
  2. un générateur est itérable. Qu'est-ce qu'un itérable ? C'est quelque chose comme un list ou set ou range ou une vue dict, avec un protocole intégré pour visiter chaque élément dans un certain ordre .

en un mot: un générateur est une liste paresseuse, en attente progressive , et yield les déclarations vous permettent d'utiliser la notation de la fonction pour programmer la liste des valeurs le générateur doit progressivement cracher.

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]]

exemple

définissons une fonction makeRange qui est exactement comme range de Python . Appeler makeRange(n) renvoie un générateur:

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 forcer le générateur à retourner immédiatement ses valeurs en attente, vous pouvez le passer dans list() (tout comme vous pourriez n'importe quel itérable):

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

la Comparaison de l'exemple de "juste retour d'une liste"

l'exemple ci-dessus peut être considéré comme une simple création d'une liste à laquelle vous annexez 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]

il y a cependant une différence majeure; voir la dernière section.


Comment vous pourriez utiliser les générateurs

un itérable est la dernière partie d'une liste

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

pour obtenir une meilleure sensation pour les générateurs, vous pouvez jouer avec le itertools module (assurez-vous d'utiliser chain.from_iterable plutôt que chain lorsque justifié). Par exemple, vous pourriez même utiliser des générateurs pour mettre en œuvre des listes paresseuses infiniment longues comme itertools.count() . Vous pouvez mettre en œuvre votre propre def enumerate(iterable): zip(count(), iterable) , ou alternativement le faire avec le mot-clé yield dans un tout en boucle.

s'il vous Plaît note: les générateurs peuvent effectivement être utilisés pour beaucoup plus de choses, telles que la mise en œuvre de coroutines ou non-déterministe de programmation ou d'autres choses élégantes. Cependant, le point de vue des" listes paresseuses " que je présente ici est l'usage le plus courant que vous trouverez.


Derrière les coulisses

c'est ainsi que fonctionne le" protocole d'itération Python". Qu'est ce qui se passe quand vous faites list(makeRange(5)) . C'est ce que j'ai décrit plus tôt comme une "liste paresseuse, incrémentielle".

>>> 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 intégrée next() appelle simplement la fonction objets .next() , qui fait partie du" protocole d'itération " et se trouve sur tous les itérateurs. Vous pouvez utiliser manuellement la fonction next() (et d'autres parties du protocole d'itération) pour mettre en œuvre des choses fantaisistes, généralement au détriment de la lisibilité, alors essayez d'éviter de le faire...


Minuties

normalement, la plupart des gens ne se soucient pas des distinctions suivantes et veulent 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 comme [1,2,3].__iter__() . A 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 à partir d'une liste, il crée un nouvel itérateur. Toutefois, lorsque vous demandez un itérateur d'un itérateur (que vous faites rarement), il vous donne simplement une copie de lui-même.

donc, dans le cas peu probable que vous ne parveniez pas à faire quelque chose comme ça...

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

... puis n'oubliez pas qu'un générateur est un itérateur , c'est-à-dire qu'il est à usage unique. Si vous voulez la réutiliser, vous devez appeler myRange(...) à nouveau. Si vous devez utiliser le résultat deux fois, convertissez le résultat en une liste et stockez-le dans une variable x = list(myRange(5)) . Ceux qui ont absolument besoin de cloner un générateur (par exemple, qui font terrifiant métaprogrammation de hackish) peuvent utiliser itertools.tee si absolument nécessaire, depuis le python iterator copiable PEP la proposition de normes a été reportée.

354
répondu ninjagecko 2017-03-19 11:07:35
la source

Que fait le mot-clé yield en Python?

Aperçu De La Réponse / Résumé

  • une fonction avec yield , lorsqu'il est appelé, renvoie un Generator .
  • générateurs sont des itérateurs parce qu'ils mettent en œuvre le protocole itérateur , de sorte que vous pouvez itérer sur eux.
  • un générateur peut aussi être informations envoyées , ce qui en fait conceptuellement un coroutine .
  • en Python 3, vous pouvez déléguer d'un générateur à l'autre dans les deux sens avec yield from .
  • (L'annexe Critique un couple de les réponses, y compris celle d'en haut, et traite de l'utilisation de return dans un générateur.)

générateurs:

yield est seulement légal à l'intérieur d'une définition de fonction, et l'inclusion de yield dans une définition de fonction le fait retourner un générateur.

l'idée de générateurs vient d'autres langues (voir référence 1) avec des variantes application. Dans les générateurs de Python, l'exécution du code est gelé au point de rendement. Lorsque le générateur est appelé (les méthodes sont discutées ci-dessous) l'exécution reprend et se fige ensuite au rendement suivant.

yield fournit un méthode facile de mettant en œuvre le protocole itérateur , défini par les deux méthodes suivantes: __iter__ et next (Python 2) ou __next__ (Python 3). Ces deux méthodes faire d'un objet un itérateur que vous pourriez taper-vérifier avec la Base abstraite Iterator Classe du module 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's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Le type de générateur est un sous-type d'itérateur:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

et si nécessaire, nous pouvons taper comme ceci:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

une caractéristique d'un Iterator est qu'une fois épuisé , vous ne pouvez pas le réutiliser ou le réinitialiser:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

vous devrez en créer un autre si vous voulez utiliser à nouveau ses fonctionnalités (voir Référence 2):

>>> list(func())
['I am', 'a generator!']

on peut obtenir des données par programme, par exemple:

def func(an_iterable):
    for item in an_iterable:
        yield item

le générateur simple ci - dessus est aussi équivalent au ci-dessous-à partir de Python 3.3 (et non disponible en Python 2), Vous pouvez utiliser yield from :

def func(an_iterable):
    yield from an_iterable

cependant, yield from permet également de déléguer 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 est une expression qui permet d'envoyer des données dans le générateur (voir Référence 3)

voici un exemple, prenez note de la variable received , qui pointera vers les données envoyées au générateur:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

tout d'abord, nous devons mettre en file d'attente le générateur avec la fonction intégrée, next . Il appelez la méthode appropriée next ou __next__ , selon la version de Python vous utilisez:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Et maintenant, nous pouvons envoyer des données dans le générateur. ( Envoyer None est la même chose qu'appeler next .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Délégation coopérative à la sous-Corotine avec yield from

maintenant, rappelez-vous que yield from est disponible en Python 3. Cela nous permet de nous déléguer coroutines à une sous-coroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

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:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

vous pouvez en savoir plus sur la sémantique précise de yield from dans PEP 380.

autres méthodes: fermer et jeter

la méthode close soulève GeneratorExit au point la fonction l'exécution a été gelé. Ce sera aussi appelé par __del__ donc vous peut mettre n'importe quel code de nettoyage où vous manipulez le GeneratorExit :

>>> my_account.close()

vous pouvez également jeter une exception qui peut être manipulée dans le générateur ou renvoyés à l'utilisateur:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Conclusion

je crois avoir couvert tous les aspects de la question suivante:

Que fait le mot-clé yield en Python?

il s'avère que yield fait beaucoup. Je suis sûr que je pourrais ajouter encore plus approfondie des exemples de cette. Si vous voulez plus ou avez des critiques constructives, laissez-moi savoir en commentant dessous.


Annexe:

Critique de la Haut/Accepté de Répondre**

  • il est confus sur ce qui fait un iterable , en utilisant juste une liste comme un exemple. Voir mes références ci-dessus, mais en résumé: un iterable a une méthode __iter__ retournant un iterator . Un iterator fournit un .next (Python 2 ou .__next__ (Python 3) méthode, qui est implicitement appelé par for boucles jusqu'à ce qu'il soulève StopIteration , et une fois qu'il le fait, il continuera à le faire.
  • il utilise ensuite une expression de générateur pour décrire ce qu'est un générateur. Puisqu'un générateur est simplement un moyen commode pour créer un iterator , il confond seulement la matière, et nous n'avons pas encore obtenu la partie yield .
  • dans commande d'une génératrice épuisement il appelle la méthode .next , alors qu'à la place il devrait utiliser la fonction intégrée, next . Il s'agirait d'un calque approprié d'indirection, car son code ne fonctionne pas en Python 3.
  • Itertools? Cela n'a rien à voir avec ce que yield fait.
  • aucune discussion sur les méthodes que yield fournit avec la nouvelle fonctionnalité yield from en Python 3. haut/accepté la réponse est très incomplète.

Critique de la réponse suggérant yield dans une expression génératrice ou la compréhension.

la grammaire permet actuellement toute expression dans une liste de compréhension.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

depuis le rendement est une expression , il a été vanté par certains comme intéressant de l'utiliser dans les compréhensions ou l'expression génératrice-en dépit de citer aucun particulièrement bon de cas d'utilisation.

Le Disponible les développeurs principaux sont discuter de la dépréciation de sa provision . Voici un post pertinent de la liste de diffusion:

le 30 janvier 2017 à 19: 05, Brett Cannon a écrit:

Sur Sun, 29 Jan 2017 à 16:39 Craig Rodrigues a écrit:

je suis D'accord avec l'une ou l'autre approche. En laissant les choses de la façon ils sont en Python 3 n'est pas bon, à mon humble avis.

mon vote est que ce soit une SyntaxError puisque vous n'obtenez pas ce que vous attendez de syntaxe.

je suis d'accord que c'est un endroit raisonnable pour nous de finir, comme n'importe quel code en s'appuyant sur le comportement actuel est vraiment trop intelligent pour être facile à entretenir.

en termes d'y arriver, nous voulons probablement:

  • mise en garde Syntaxeou dévalorisation en 3.7
  • avertissement Py3k en 2.7.x
  • SyntaxError 3.8

Santé, Nick.

-- Nick Coghlan / ncoghlan à gmail.com / Brisbane, Australie

en outre, il y a une question en suspens (10544) qui semble pointer dans la direction de cette jamais être une bonne idée (PyPy, une implémentation de Python écrite en Python, est déjà en train d'augmenter les avertissements de syntaxe.)

ligne de fond, jusqu'à ce que les développeurs de CPython nous disent le contraire: ne mettez pas yield dans une expression de générateur ou la compréhension.

Le return déclaration dans un générateur

In Python 2 :

dans une fonction génératrice, la mention return n'est pas autorisée pour inclure un expression_list . Dans ce contexte, un simple return indique que le générateur est terminé et fera monter StopIteration .

un expression_list est essentiellement n'importe quel nombre d'expressions séparées par des virgules - essentiellement, en Python 2, Vous pouvez arrêter le générateur avec return , mais vous ne pouvez pas retourner une valeur.

In Python 3 :

dans une fonction génératrice, l'énoncé return indique que la génératrice est terminée et qu'elle fera monter StopIteration . La valeur retournée (le cas échéant) est utilisée comme argument pour construire StopIteration et devient l'attribut StopIteration.value .

notes

  1. les langues CLU, Sather, et Icon ont été mentionnés dans la proposition pour présenter le concept de générateurs à Python. L'idée générale est qu'une fonction peut maintenir l'état interne et de rendement intermédiaire points de données à la demande de l'utilisateur. Cela a promis d'être supérieur dans la performance à d'autres approches, y compris Python threading , qui n'est même pas disponible sur certains systèmes.

  2. Ce signifie, par exemple, que les objets xrange ( range en Python 3) ne sont pas Iterator , même s'ils sont itérables, parce qu'ils peuvent être réutilisés. Comme les listes, leurs méthodes __iter__ renvoient des objets itérateurs.

  3. yield a été initialement présenté comme une déclaration, ce qui signifie qu'il ne peut apparaître au début d'une ligne dans un bloc de code. Maintenant yield crée une expression de rendement. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Ce changement a été proposé pour permettre à un utilisateur d'envoyer des données dans le générateur comme on pourrait le recevoir. Pour envoyer des données, il faut pouvoir l'attribuer à quelque chose, et pour ça, une déclaration ne marchera pas.

263
répondu Aaron Hall 2017-12-06 23:20:40
la source

yield est comme return - il renvoie ce que vous lui dites (comme un 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 à la déclaration yield . Contrairement à return, le cadre de la pile n'est pas nettoyé quand un rendement se produit, cependant le contrôle est transféré de nouveau à l'appelant, de sorte que son état reprendra la prochaine fois la fonction.

Dans le cas de votre code, le la fonction "151930920 est d'agir comme un itérateur de sorte que lorsque vous étendez votre liste, il ajoute un élément à la fois à la nouvelle liste.

list.extend appelle un itérateur jusqu'à épuisement. Dans le cas de l'échantillon de code que vous avez posté, il serait beaucoup plus clair de simplement retourner un tuple et l'ajouter à la liste.

235
répondu Douglas Mayle 2018-05-20 12:57:33
la source

il y a une chose supplémentaire à mentionner: une fonction qui cède n'a pas besoin de se terminer. J'ai écrit un code comme ceci:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

alors je peux l'utiliser dans un autre code comme celui-ci:

for f in fib():
    if some_condition: break
    coolfuncs(f);

il aide vraiment à simplifier certains problèmes, et rend certaines choses plus faciles à travailler avec.

186
répondu Claudiu 2013-04-21 19:42:14
la source

pour ceux qui préfèrent un exemple de travail minimal, méditez sur cette session interactive Python session:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
156
répondu Daniel 2013-01-25 23:19:12
la source
Le rendement

vous donne un générateur.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

comme vous pouvez le voir, dans le premier cas foo garde la liste entière en mémoire à la fois. C'est pas une grosse affaire pour une liste avec les 5 éléments, mais si vous voulez une liste de 5 millions de dollars? Non seulement c'est un mangeur de mémoire énorme, il en coûte aussi beaucoup de temps à construire au moment où la fonction est appelée. Dans le second cas, bar vous donne juste un générateur. Un générateur est itérable--ce qui signifie que vous pouvez l'utiliser dans une 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 était dans la boucle la dernière fois que vous l'Avez Appelé--de cette façon, si vous utilisez un itérable pour (dire) compter jusqu'à 50 milliards, vous ne devez pas compter jusqu'à 50 milliards d'un seul coup et stocker les 50 milliards de nombres à compter. Encore une fois, c'est un exemple assez artificiel, vous utiliseriez probablement itertools si vous vouliez vraiment compter jusqu'à 50 milliards. :)

c'est le cas le plus simple d'utilisation des générateurs. Comme vous l'avez dit, il peut être utilisé pour écrire des permutations efficaces, en utilisant le rendement 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 la traversée des arbres spécialisés, et toutes sortes d'autres choses.

135
répondu RBansal 2013-01-16 10:42:09
la source

il renvoie un générateur. Je ne suis pas particulièrement familier avec Python, mais je crois que c'est le même genre de chose que les blocs itérateurs de C# si vous êtes familier avec ceux-là.

il y a un article D'IBM qui l'explique assez bien (pour Python) autant que je puisse voir.

L'idée clé est que le compilateur/interpréteur/qu'est une ruse de sorte que dans la mesure où l'appelant est concerné, ils peuvent continuer à appeler next () et il va continuer à retourner les valeurs - comme si la méthode du générateur était en pause . Maintenant évidemment, vous ne pouvez pas vraiment "mettre en pause" une méthode, donc le compilateur construit une machine d'État pour vous rappeler 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.

127
répondu Jon Skeet 2008-10-24 02:26:06
la source

TL; DR

au lieu de ceci:

def squares_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

faites ceci:

def squares_the_yield_way(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

chaque fois que vous vous trouvez à construire une liste à partir de zéro, yield chaque pièce à la place.

c'était mon premier moment "aha" avec le rendement.


yield est une façon sucrée de dire

construire une série de choses

même comportement:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

comportement différent:

Rendement single-pass : vous ne pouvez itérer une fois. Quand une fonction a un rendement en elle, nous l'appelons une fonction génératrice . Et un iterator est ce qu'il retourne. C'est révélateur. Nous perdons la commodité d'un conteneur, mais gagner la puissance d'une série arbitrairement longue.

rendement est paresseux , il met hors calcul. Une fonction avec un rendement en elle n'exécute pas réellement du tout quand vous l'appelez. L'objet itérateur qu'il retourne utilise magic pour maintenir le contexte interne de la fonction. Chaque fois que vous appelez next() sur l'itérateur (cela se produit dans une boucle) exécution pouces vers l'avant au rendement suivant. ( return relèves StopIteration et termine la série.)

Rendement polyvalent . Il peut faire des boucles infinies:

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Si vous avez besoin d' multiples entrées et la série n'est pas trop long, il suffit d'appeler list() :

>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]

Brillant choix du mot yield parce que les deux significations de s'applique:

"1519130920 le" rendement - produire ou fournir (comme dans l'agriculture)

...fournir les données suivant dans la série.

"1519130920 le" rendement - donnez manière ou d'abandonner (comme dans le pouvoir politique)

...abandonnez l'exécution CPU jusqu'à ce que l'itérateur avance.

126
répondu Bob Stein 2018-09-07 20:15:54
la source

il y a un type de réponse que je ne pense pas avoir été donné encore, parmi les nombreuses grandes réponses qui décrivent comment utiliser des générateurs. Voici la réponse de la théorie du langage de programmation:

l'instruction yield en Python renvoie un générateur. Un générateur en Python est une fonction qui retourne continuations (et spécifiquement un type de coroutine, mais les continuations représentent le mécanisme plus général pour comprendre ce qui va sur.)

Poursuites dans les langages de programmation théorie sont beaucoup plus fondamental genre de calcul, mais ils ne sont pas souvent utilisés, car ils sont extrêmement difficiles à comprendre et aussi très difficile à mettre en œuvre. Mais l'idée de ce qu'est une continuation est, est simple: c'est l'état d'un calcul qui n'a pas encore fini. Dans cet état, les valeurs actuelles des variables, les opérations qui n'ont pas encore été effectué, et ainsi de suite, sont enregistrés. Puis à un certain point plus tard dans le programme la continuation peut être invoquée, de sorte que les variables du programme sont réinitialisées à cet état et les opérations qui ont été sauvegardées sont effectuées.

Continuations, dans ce plus générale, peut être mis en œuvre de deux façons. De la manière call/cc , la pile du programme est littéralement sauvegardée et lorsque la suite est invoquée, la pile est restaurée.

dans le style continuationpassing (CPS), les continuations sont tout simplement normales fonctions (uniquement dans les langages où les fonctions sont de première classe) que le programmeur gère explicitement et transmet aux sous-routines. Dans ce style, l'état du programme est représenté par des fermetures (et les variables qui s'y trouvent encodé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 argument (dans certaines variantes de CPS, les fonctions peuvent accepter plusieurs continuations) et manipulent le flux de contrôle en les invoquant par il suffit de les appeler et de revenir après. Un exemple très simple de style de passage de continuation est comme suit:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

dans cet exemple (très simpliste), le programmeur sauve l'opération d'écriture réelle du fichier dans une continuation (qui peut potentiellement être une opération très complexe avec beaucoup de détails à écrire), et passe ensuite cette continuation (I. e, comme une fermeture de première classe) à un autre opérateur qui fait un peu plus de traitement, puis l'appelle si nécessaire. (J'utilise ce modèle de conception beaucoup dans la programmation GUI réelle, soit parce qu'il me sauve des lignes de code ou, plus important encore, pour gérer le flux de contrôle après le déclenchement d'événements GUI.)

le reste de ce post va, sans perte de généralité, conceptualiser les continuations comme CPS, parce qu'il est un enfer de 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 suite. Alors que les continuations sont capables en général de sauver l'état d'un calcul (c.-à-d., la pile d'appels du programme), générateurs sont seulement capables de sauver l'état d'itération 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:

def f():
  while True:
    yield 4

This est clairement raisonnable itératif dont le comportement est bien défini, chaque fois que le générateur parcourt, il renvoie 4 (et fait toujours). Mais ce n'est probablement pas le type prototypique d'itérable qui vient à l'esprit en pensant aux itérateurs (i.e., for x in collection: do_something(x) ). Cet exemple illustre la puissance des générateurs: si quelque chose est un itérateur, un générateur peut enregistrer l'état de son itération.

pour réitérer: les Continuations peuvent sauver l'état de la pile d'un programme et les générateurs peuvent sauver l'état d'itération. Cela signifie que les suites sont plus beaucoup puissant que ceux des producteurs, mais aussi que les générateurs sont beaucoup, beaucoup plus facile. Ils sont plus faciles à implémenter pour le concepteur de langage, et plus faciles à utiliser pour le programmeur (si vous avez le temps de graver, essayez de lire et de comprendre cette page sur les continuations et appelez/cc ).

mais vous pourriez facilement mettre en œuvre (et conceptualiser) des générateurs comme un cas simple et spécifique de continuation passant style:

chaque fois que yield est appelé, il indique à la fonction de retourner une continuation. Lorsque la fonction est appelée à nouveau, elle commence là où elle s'est arrêtée. Ainsi, en pseudo-pseudocode (c.-à-d. pas de pseudocode, mais pas de code) la méthode next du générateur est fondamentalement comme suit:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

où le mot-clé yield est en fait du sucre syntaxique pour la fonction de générateur réel, essentiellement quelque chose comme:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

rappelez-vous que ce n'est que du pseudocode et que l'implémentation actuelle 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 les objets generator sans utiliser le mot-clé yield .

123
répondu aestrivex 2018-05-20 13:25:32
la source

Voici un exemple en langage clair. Je fournirai une correspondance entre des concepts humains de haut niveau et des 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:

  • je vous appelle pour vous dire que je veux une séquence de nombres qui est produit dans un de façon spécifique, et je vous fais savoir quel est l'algorithme.

    cette étape correspond à def dans la fonction génératrice, c'est-à-dire la fonction contenant un yield .
  • un peu plus tard, je vous dis, "ok, préparez-vous à me dire la séquence des nombres".

    cette étape correspond à l'appel de la fonction génératrice qui renvoie un objet générateur. notez que vous ne me donne pas encore de chiffres, prends ton papier et ton crayon.
  • je vous demande: "dites-moi le nombre suivant", et vous dites-moi le premier nombre; après cela, vous attendez pour moi de vous demander pour le prochain numéro. C'est votre travail de vous rappeler où vous étiez, quels sont les chiffres que vous avez déjà indiqués, et quel est le prochain chiffre. Je ne me préoccupe pas des détails.

    cette étape correspond à l'appel .next() sur l'objet générateur.
  • ... répéter l'étape précédente, jusqu'à ...
  • vous pourriez finir. Vous ne me dites pas un chiffre, vous criez juste, " hold your horses! Je suis fait! Pas de numéros de plus!"

    cette étape correspond à l'objet générateur mettant fin à son travail, et soulevant une StopIteration exception la fonction générateur n'a pas besoin de soulever l'exception. C'est augmentée automatiquement lorsque la fonction se termine ou un return .

C'est ce que fait un générateur (une fonction qui contient un yield ); il commence l'exécution, s'arrête chaque fois qu'il fait un yield , et quand on lui demande une valeur .next() il continue du point où il était dernier. Il s'adapte parfaitement par conception avec le protocole iterator de Python, qui décrit comment demander des valeurs séquentiellement.

l'utilisateur le plus célèbre du protocole iterator est la commande for en Python. Donc, chaque fois que vous faites un:

for item in sequence:

cela n'a pas d'importance si sequence est une liste, une chaîne, un dictionnaire ou un générateur objet comme décrit ci-dessus; le résultat est le même: vous lisez les éléments d'une séquence un par un.

notez que def dans une fonction qui contient un mot-clé yield n'est pas la seule façon de créer un générateur; c'est juste la façon la plus facile d'en créer un.

pour des informations plus précises, lisez à propos de types d'itérateurs , la déclaration de rendement et générateurs dans la documentation Python.

109
répondu tzot 2018-05-20 13:06:05
la source

bien que beaucoup de réponses montrent pourquoi vous utiliseriez un yield pour créer un générateur, il y a plus d'utilisations pour yield . Il est assez facile de faire une coroutine, qui permet la transmission d'informations entre deux blocs de code. Je ne vais pas répéter les beaux exemples qui ont déjà été donnés sur l'utilisation de yield pour créer un générateur.

pour aider à comprendre ce qu'un yield fait dans le code suivant, vous pouvez utiliser votre doigt pour tracer parcourez n'importe quel code qui a un yield . Chaque fois que votre doigt touche le yield , vous devez attendre qu'un next ou un send soit entré. Quand un next est appelé, vous tracez à travers le code jusqu'à ce que vous appuyez sur le yield ... le code sur la droite du yield est évalué et retourné à l'appelant... puis vous attendez. Quand next est appelé à nouveau, vous effectuez une autre boucle à travers le code. Cependant, vous noterez que dans une coroutine, yield peut également être utilisé avec un send ... qui enverra une valeur de l'appelant dans la fonction de rendement. Si un send est donné, alors yield reçoit la valeur envoyée, et la crache du côté gauche... alors la trace à travers le code progresse jusqu'à ce que vous frappez à nouveau le yield (retourner la valeur à la fin, comme si next était appelé).

par exemple:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
97
répondu Mike McKerns 2014-02-04 06:27:35
la source

il y a un autre yield usage et sens (depuis Python 3.3):

yield from <expr>

From PEP 380 -- syntaxe for Delegating to a Subgenerator :

une syntaxe est proposée Pour qu'un générateur délègue une partie de ses opérations à un autre générateur. Cela permet à une section de code contenant 'yield' d'être prise en compte et placée dans un autre générateur. En outre, l' le sous-générateur est autorisé à revenir avec une valeur, et la valeur est mise à la disposition du générateur délégant.

la nouvelle syntaxe ouvre également des possibilités d'optimisation lorsqu'un générateur redonne des valeurs produites par un autre.

de plus ce introduira (depuis Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

pour éviter que les coroutines soient confondues avec un générateur régulier (aujourd'hui yield est utilisé dans les deux).

86
répondu Sławomir Lenart 2018-05-20 13:34:03
la source

j'allais le poster "lire en page 19 de Beazley" Python: ouvrage de Référence Indispensable pour une description rapide des générateurs", mais beaucoup d'autres ont affiché de bonnes descriptions déjà.

aussi, notez que yield peut être utilisé dans les coroutines comme dual de leur utilisation dans les fonctions de générateur. Bien que ce ne soit pas la même utilisation que votre code snippet, (yield) peut être utilisé comme une expression dans une fonction. Lorsqu'un appelant envoie une valeur à la méthode en utilisant le send() méthode, puis la coroutine s'exécutera jusqu'à ce que la prochaine instruction (yield) soit rencontrée.

générateurs et coroutines sont un moyen cool de mettre en place des applications de type flux de données. J'ai pensé qu'il serait intéressant de connaître l'autre utilisation de la yield déclaration dans les fonctions.

77
répondu johnzachary 2013-01-28 05:37:10
la source

voici quelques exemples de Python pour mettre en œuvre des générateurs comme si Python ne leur fournissait pas de sucre syntaxique:

comme générateur de Python:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

utilisant des fermetures lexicales au lieu de générateurs

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

utilisant des fermetures d'objets au lieu de générateurs (parce que fermetures et rejets sont équivalents )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
77
répondu Dustin Getz 2017-10-24 13:46:05
la source

du point de vue de la programmation, les itérateurs sont implémentés en tant que thunks .

pour mettre en œuvre des groupes d'itérateurs, de générateurs et de threads pour l'exécution simultanée, etc. en tant que thunks (aussi appelé fonctions anonymes), on utilise des messages envoyés à un objet de fermeture, qui a un répartiteur, et le répartiteur répond aux "messages".

http://en.wikipedia.org/wiki/Message_passing

suivant "est un message envoyé à une fermeture, créé par l'appel" iter ".

il y a beaucoup de façons d'implémenter ce calcul. J'ai utilisé de la mutation, mais il est facile de le faire sans mutation, en retournant la valeur actuelle et la prochaine productrice.

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 requis pour le réécrire en Python.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
70
répondu alinsoar 2018-05-20 13:29:34
la source

Toutes les grandes réponses, cependant un peu difficile pour les débutants.

je suppose que vous avez appris la déclaration return .

comme analogie, return et yield sont des jumeaux. return signifie "retour et arrêt" alors que "rendement" signifie "retour, mais continuer'

  1. Essayer d'obtenir un num_list avec return .
def num_list(n):
    for i in range(n):
        return i

de l'Exécuter:

In [5]: num_list(3)
Out[5]: 0

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 heureusement, il suffit de mettre en œuvre une fois et d'arrêter.

  1. Il vient yield

remplacer return par yield :

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Maintenant, vous gagnez pour obtenir tous les numéros.

par rapport à return qui court Une fois et s'arrête, yield qui court des fois que vous planifiez. Vous pouvez interpréter return comme return one of them , et yield comme return all of them . Cela s'appelle iterable .

  1. un pas de plus nous pouvons réécrire yield déclaration avec return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

c'est le noyau de yield .

le différence entre une liste return sorties et l'objet yield sortie est:

vous obtiendrez toujours [0, 1, 2] à partir d'un objet list, mais vous ne pourrez les récupérer qu'une seule fois à partir de 'l'objet yield output'. Ainsi, il a un nouveau nom generator objet comme affiché dans Out[11]: <generator object num_list at 0x10327c990> .

en conclusion, comme métaphore pour grok it:

  • return et yield sont jumeaux
  • list et generator sont jumeaux
66
répondu JawSaw 2018-05-28 12:06:22
la source

voici un exemple simple:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

sortie:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Je ne suis pas un développeur Python, mais il me semble que yield maintient la position du flux de programme et la prochaine boucle commence à partir de la position" rendement". Il semble qu'il attend à cette position, et juste avant cela, retournant une valeur à l'extérieur, et la prochaine fois continue à travailler.

il semble être une capacité intéressante et agréable :d

63
répondu Engin OZTURK 2018-05-20 13:31:01
la source

Voici une image mentale de ce que yield fait.

j'aime penser à un thread comme ayant une pile (même s'il n'est pas implémenté de cette façon).

quand une fonction normale est appelée, elle met ses variables locales sur la pile, fait quelques calculs, puis efface la pile et retourne. Les valeurs de ses variables locales ne sont plus jamais vues.

avec une fonction yield , lorsque son code commence à courir (i.e. après que la fonction est appelée, retournant un objet générateur, dont la méthode next() est alors invoquée), elle met de même ses variables locales sur la pile et calcule pendant un certain temps. Mais ensuite, quand il frappe la déclaration yield , avant de vider sa partie de la pile et de revenir, il prend un instantané de ses variables locales et les stocke dans l'objet generator. Il note également l'endroit où il est actuellement jusqu'à dans son code (i.e. le particulier yield déclaration).

donc c'est une sorte de fonction gelée sur laquelle le générateur est accroché.

lorsque next() est appelé par la suite, il récupère les biens de la fonction sur la pile et la réanime. La fonction continue de calculer à partir de là où elle s'est arrêtée, ignorant le fait qu'elle venait de passer une éternité en entrepôt frigorifique.

comparez les exemples suivants:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

quand on appelle la deuxième fonction, il se comporte très différemment de la première. La déclaration yield peut être inaccessible, mais si elle est présente quelque part, elle change la nature de ce que nous traitons.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

appelant yielderFunction() ne exécute pas son code, mais fait un générateur hors du code. (Peut-être que c'est une bonne idée de nommer ces choses avec le préfixe yielder pour la lisibilité.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

Le gi_code et gi_frame champs où l'état congelé est stockée. En les explorant avec dir(..) , nous pouvons confirmer que notre modèle mental ci-dessus est crédible.

54
répondu Evgeni Sergeev 2017-03-01 16:36:58
la source

comme chaque réponse suggère, yield est utilisé pour créer un générateur de séquence. Il est utilisé pour générer une séquence de façon dynamique. Par exemple, en lisant un fichier ligne par ligne sur un réseau, vous pouvez utiliser la fonction yield comme suit:

def getNextLines():
   while con.isOpen():
       yield con.read()

Vous pouvez l'utiliser dans votre code comme suit:

for line in getNextLines():
    doSomeThing(line)

transfert du contrôle D'exécution gotcha

le contrôle d'exécution sera transféré de getNextLines() à la boucle for lorsque le rendement est exécuté. Ainsi, chaque fois que getNextLines() est invoqué, l'exécution commence à partir du point où elle a été interrompue la dernière fois.

ainsi en bref, une fonction avec le code suivant

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

imprimé

"first time"
"second time"
"third time"
"Now some useful value 12"
42
répondu Mangu Singh Rajpurohit 2018-05-20 13:42:59
la source

le Rendement est un objet

A return dans une fonction retournera une seule valeur.

si vous voulez une fonction pour retourner un énorme ensemble de valeurs , utilisez yield .

plus important encore, yield est une barrière .

comme barrière dans la langue CUDA, il ne transférera pas le contrôle jusqu'à ce qu'il obtienne compléter.

C'est-à-dire qu'il exécutera le code dans votre fonction du début jusqu'à ce qu'il atteigne 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, retournant la valeur suivante jusqu'à ce qu'il n'y ait aucune valeur à retourner.

38
répondu Kaleem Ullah 2018-05-20 13:45:50
la source

en résumé, la déclaration yield transforme votre fonction en une usine qui produit un objet spécial appelé generator qui enveloppe le corps de votre fonction originale. Lorsque le generator est itéré, il exécute votre fonction jusqu'à ce qu'elle atteigne le yield suivant alors suspend l'exécution et évalue à la valeur passée à yield . Il répète ce processus à chaque itération jusqu'à ce que le chemin d'exécution quitte la fonction. Par exemple,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

sorties simples

one
two
three

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 s'arrêtant à chaque fois pour "céder" le résultat suivant du calcul, de cette façon il calcule une liste à la volée, l'avantage étant la mémoire enregistrée pour des calculs particulièrement grands

dites que vous vouliez créer votre propre fonction range qui produit une gamme itérable de nombres, vous pourriez le faire comme cela,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

et l'utiliser comme ceci;

for i in myRangeNaive(10):
    print i

mais c'est inefficace parce que

  • vous créez un tableau que vous n'utilisez qu'une seule fois (cela gaspille la mémoire)
  • ce code fait deux boucles sur ce tableau! : (

heureusement Guido et son équipe ont été assez généreux pour développer des générateurs afin nous pourrions nous contenter de cela;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

maintenant, à chaque itération, une fonction du générateur appelée next() exécute la fonction jusqu'à ce qu'elle atteigne une instruction "yield" dans laquelle elle s'arrête et "yields" la valeur ou la fin de la fonction. Dans ce cas, le premier appel, next() exécute jusqu'à l'instruction rendement et le rendement 'n', sur le prochain appel il va exécuter l'incrément déclaration, revenir à la "tandis que", de l'évaluer, et si cela est vrai, va s'arrêter et céder 'n' encore, il va continuer de cette façon jusqu'à ce que la condition while retourne false et le générateur saute à la fin de la fonction.

37
répondu redbandit 2018-05-20 14:04:36
la source

yield est comme un élément de retour pour une fonction. La différence est que l'élément yield transforme une fonction en générateur. Un générateur se comporte comme une fonction jusqu'à ce que quelque chose est "fourni". Le générateur s'arrête jusqu'à ce qu'il soit 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 seule, en appelant list(generator()) .

34
répondu An Epic Person 2015-05-20 09:19:32
la source

(ma réponse ci-dessous ne parle que du point de vue de l'utilisation du générateur de Python, pas le implémentation sous-jacente du mécanisme du générateur , qui implique quelques astuces de la pile et de la manipulation de tas.)

quand yield est utilisé à la place d'un return dans une fonction python, cette fonction est transformée en quelque chose de spécial appelé generator function . Cette fonction retournera un objet de type generator . le Le mot-clé yield est un drapeau pour notifier au compilateur python de traiter spécialement une telle fonction. les fonctions normales prendront fin une fois qu'une certaine valeur en sera retournée. Mais avec l'aide du compilateur, la fonction de générateur peut être considérée comme comme réutilisable. Qui est, le contexte d'exécution sera rétablie et que l'exécution se poursuit à partir de la dernière exécution. Jusqu'à ce que vous appeliez explicitement return, ce qui soulèvera une exception StopIteration (qui fait également partie de la itérateur protocole), ou atteindre la fin de la fonction. J'ai trouvé beaucoup de références sur generator mais ce un du functional programming perspective est le plus digeste.

(maintenant je veux parler de la raison derrière generator , et le iterator basé sur ma propre compréhension. J'espère que cela peut vous aider à saisir le motivation essentielle de l'itérateur et du générateur. Ce concept apparaît dans d'autres langages comme C#.)

comme je le comprends, quand nous voulons traiter un tas de données, nous stockons habituellement d'abord les données quelque part et puis les traiter un par un. Mais cette approche intuitive est problématique. Si le volume de données est énorme, il est coûteux de les stocker dans leur ensemble à l'avance. donc au lieu de stocker le data lui-même directement, pourquoi ne pas stocker une sorte de metadata indirectement, c'est à dire the logic how the data is computed .

il existe deux approches pour envelopper de telles métadonnées.

  1. OO approche, nous envelopper les métadonnées as a class . C'est ce qu'on appelle iterator qui implémente le protocole iterator (c'est-à-dire les méthodes __next__() et __iter__() ). C'est aussi couramment rencontrés itérateur modèle de conception .
  2. l'approche fonctionnelle, nous enveloppons les métadonnées as a function . C'est le soi-disant generator function . Mais sous le capot, le retour generator object encore IS-A iterator parce qu'il met également en œuvre le protocole iterator.

dans tous les cas, un itérateur est créé, c'est-à-dire un objet qui peut vous donner les données que vous voulez. L'approche de L'OO peut être un peu complexe. De toute façon, lequel utiliser dépend de vous.

34
répondu smwikipedia 2017-05-23 15:02:54
la source

beaucoup de gens utilisent return plutôt que yield , mais dans certains cas yield peut être plus efficace et plus facile à travailler avec.

Voici un exemple qui yield est certainement le meilleur pour:

retour (en fonction)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

"1519160920 le" rendement (en fonction)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

fonctions D'appel

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

les deux fonctions font la même chose, mais yield utilise trois lignes au lieu de cinq et a une variable de moins à se soucier.

résultat du code:

Output

comme vous pouvez le voir les deux fonctions font la même chose. La seule différence est return_dates() donne une liste et yield_dates() donne un générateur.

Un exemple réel serait quelque chose comme la lecture d'un fichier ligne par ligne ou si vous voulez juste faire un générateur.

34
répondu Tom Fuller 2018-05-20 14:02:52
la source

le mot-clé yield recueille simplement des résultats de retour. Pensez à yield comme return +=

33
répondu Bahtiyar Özdere 2016-04-23 06:16:19
la source

Voici une simple yield approche basée, pour calculer la série fibonacci, expliqué:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

quand vous entrez ceci dans votre REPL et ensuite essayer de l'appeler, vous obtiendrez un résultat mystifiant:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

C'est parce que la présence de yield signalé à Python que vous souhaitez créer un générateur , c'est un objet qui génère des valeurs sur demande.

Alors, comment générez-vous ces valeurs? Cela peut être fait soit directement en utilisant la fonction intégrée next , soit indirectement en l'alimentant à une construction qui consomme des valeurs.

en utilisant la fonction intégrée next() , vous invoquez directement .next / __next__ , forçant le générateur à produire une valeur:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

indirectement, si vous fournissez fib à une boucle for , un initialiseur list , un tuple initialiseur, ou toute autre chose 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 (et qu'il retourne):

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

de même, avec un tuple initialiseur:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Un générateur diffère d'une fonction dans le sens qu'il est paresseux. Il accomplit ceci en maintenant son état local et en vous permettant de reprendre quand vous en avez besoin.

quand vous invoquez pour la première fois fib en l'appelant:

f = fib()

Python compile la fonction, rencontre le mot-clé yield et vous renvoie simplement un objet générateur. Pas très utile, il me semble.

Lorsque vous demandez alors 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 , puis cède la valeur que vous fourni à yield et les pauses. Pour un exemple qui le démontre mieux, utilisons quelques appels print (remplacez par print "text" si vous utilisez Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

maintenant, entrer dans la REPL:

>>> gen = yielder("Hello, yield!")

vous avez un objet generator qui attend une commande pour qu'il génère une valeur. Utilisez next et voyez ce qui est imprimé:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

les résultats non cités sont ceux qui sont imprimés. Le résultat cité est ce qui est retourné de yield . Appelez next encore une fois maintenant:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

le Générateur se souvient qu'il a été mis en pause à yield value et reprend à partir de là. Le message suivant est imprimé et la recherche de la déclaration yield pour s'y arrêter est effectuée à nouveau (en raison de la boucle while ).

29
répondu Jim Fasarakis Hilliard 2017-07-12 15:44:24
la source

encore un autre TL; DR

Itérateur sur la liste : next() retourne l'élément suivant de la liste

générateur D'itérateur : next() calculera l'élément suivant au vol (code d'exécution)

vous pouvez voir le rendement / générateur comme un moyen d'exécuter manuellement le flux de contrôle de l'extérieur (comme boucle continue one step), en appelant next , toutefois complexe de l'écoulement.

Note : la génératrice est et non une fonction normale. Il se souvient de l'état précédent comme des variables locales (pile). Voir d'autres réponses ou articles pour une explication détaillée. Le générateur ne peut être itéré qu'une fois . On pourrait se passer de yield , mais ce ne serait pas aussi agréable, donc on peut le considérer comme du sucre de langue "très bon".

21
répondu Christophe Roussy 2018-05-20 13:58:25
la source

Autres questions sur python iterator generator yield coroutine