11417 votes

Que fait le mot clé "yield" ?

Quelle est l'utilité de la 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 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é ? Une liste est-elle retournée ? Un seul élément ? Est-il appelé à nouveau ? Quand les appels suivants s'arrêteront-ils ?


1. Ce morceau de code a été écrit par Jochen Schulz (jrschulz), qui a créé une excellente bibliothèque Python pour les espaces métriques. Voici le lien vers la source complète : [Module mspace][1].

10 votes

Le rendement n'est pas magique comme le suggère la réponse du haut. Excellent commentaire de @mattias-fripp : When you call a function that has a yield statement, you get a generator object, but no code runs. Then each time you extract an object from the generator, Python executes the function until it reaches a yield statement, then pauses and delivers the object. When you extract another object, Python resumes just after the yield and continues until it reaches another yield (often the same one, but one iteration later). This continues until the function runs off the end, at which point the generator is deemed exhausted.

16232voto

e-satis Points 146299

Pour comprendre ce que yield fait, vous devez comprendre ce que générateurs sont. Et avant de pouvoir comprendre les générateurs, vous devez comprendre itérables .

Iterables

Lorsque vous créez une liste, vous pouvez lire ses éléments un par un. La lecture de ses éléments un par un est appelée itération :

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

mylist est un itérable . Lorsque vous utilisez une compréhension de liste, vous créez une liste, et donc un itérable :

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

Tout ce que vous pouvez utiliser " for... in... " on est une itérable ; lists , strings , des fichiers...

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

Générateurs

Les générateurs sont des itérateurs, une sorte d'itérable. vous ne pouvez itérer qu'une seule fois . Les générateurs ne stockent pas toutes les valeurs en mémoire, ils génèrent les valeurs à la volée :

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

C'est exactement la même chose, sauf que vous avez utilisé () au lieu de [] . MAIS, vous ne peut pas effectuer for i in mygenerator une seconde fois puisque 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, un par un.

Rendement

yield est un mot clé qui est utilisé comme return sauf que la fonction renvoie un générateur.

>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Il s'agit ici d'un exemple inutile, mais il est pratique lorsque vous savez que votre fonction renverra un énorme ensemble de valeurs que vous n'aurez besoin de lire qu'une seule fois.

Pour maîtriser yield vous devez comprendre que lorsque vous appelez la fonction, le code que vous avez écrit dans le corps de la fonction ne s'exécute pas. La fonction ne renvoie que l'objet générateur, c'est un peu délicat.

Ensuite, votre code reprendra là où il s'est arrêté à chaque fois. for utilise le générateur.

Maintenant, la partie difficile :

La première fois que le for appelle l'objet générateur créé à partir de votre fonction, il exécutera le code de votre fonction depuis le début jusqu'à ce qu'il touche yield puis elle renverra la première valeur de la boucle. Ensuite, chaque appel suivant exécutera une autre itération de la boucle que vous avez écrite dans la fonction et renverra la valeur suivante. Ce processus se poursuivra jusqu'à ce que le générateur soit considéré comme vide, ce qui se produit lorsque la fonction s'exécute sans frapper yield . Cela peut être dû au fait que la boucle est arrivée à son terme, ou que vous ne satisfaites plus à un critère de sélection. "if/else" .


Votre code expliqué

Générateur :

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if the distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if the distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Un appel :

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidate's list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Ce code contient plusieurs parties intelligentes :

  • La boucle itère sur une liste, mais la liste s'agrandit pendant l'itération de la boucle. C'est un moyen concis de parcourir toutes ces données imbriquées même si c'est un peu dangereux puisque vous pouvez vous retrouver avec une boucle infinie. Dans ce cas, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) épuiser toutes les valeurs du générateur, mais while continue à créer de nouveaux objets générateurs qui produiront des valeurs différentes des précédentes puisqu'il n'est pas appliqué sur le même nœud.

  • Le site extend() est une méthode d'objet liste qui attend un itérable et ajoute ses valeurs à la liste.

En général, on lui passe une liste :

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

Mais dans votre code, il obtient un générateur, ce qui est bien car :

  1. Vous n'avez pas besoin de lire les valeurs deux fois.
  2. Vous avez peut-être beaucoup d'enfants et vous ne voulez pas qu'ils soient tous stockés en mémoire.

Et cela fonctionne parce que Python ne se soucie pas de savoir si l'argument d'une méthode est une liste ou non. Python s'attend à des itérables, donc cela fonctionnera avec des chaînes de caractères, des listes, des tuples et des générateurs ! C'est ce qu'on appelle le typage en 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 plus loin pour voir une utilisation avancée d'un générateur :

Contrôler l'épuisement d'un générateur

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

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. Vous avez déjà souhaité dupliquer un générateur ? Enchaîner deux générateurs ? Grouper des valeurs dans une liste imbriquée en une seule ligne ? Map / Zip sans créer une autre liste ?

Ensuite, juste import itertools .

Un exemple ? Voyons les ordres d'arrivée possibles pour une course à quatre chevaux :

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Comprendre les mécanismes internes de l'itération

L'itération est un processus qui implique des itérables (mettant en œuvre la fonction __iter__() ) et des itérateurs (mettant en œuvre la méthode __next__() ). Les itérables sont tous les objets à partir desquels vous pouvez obtenir un itérateur. Les itérateurs sont des objets qui vous permettent d'itérer sur des itérables.

Vous trouverez plus d'informations à ce sujet dans cet article sur comment for travail en boucle .

523 votes

yield n'est pas aussi magique que cette réponse le suggère. Lorsque vous appelez une fonction qui contient un yield n'importe où, vous obtenez un objet générateur, mais aucun code ne s'exécute. Ensuite, chaque fois que vous extrayez un objet du générateur, Python exécute le code dans la fonction jusqu'à ce qu'il arrive à un objet de type yield déclaration, puis fait une pause et livre l'objet. Lorsque vous extrayez un autre objet, Python reprend juste après l'instruction yield et continue jusqu'à ce qu'il atteigne un autre yield (souvent le même, mais une itération plus tard). Ce processus se poursuit jusqu'à ce que la fonction se termine, auquel cas le générateur est considéré comme épuisé.

57 votes

"Ces itérables sont pratiques... mais vous stockez toutes les valeurs en mémoire et ce n'est pas toujours ce que vous voulez", est soit faux, soit confus. Un itérable renvoie un itérateur lors de l'appel de la fonction iter() sur l'itérable, et un itérateur ne doit pas toujours stocker ses valeurs en mémoire, selon l'implémentation de la fonction iter il peut également générer des valeurs dans la séquence à la demande.

4 votes

Il serait bon d'ajouter à cela grand réponse pourquoi C'est exactement la même chose, sauf que vous avez utilisé () au lieu de [] en particulier ce que () est (il peut y avoir confusion avec un tuple).

2234voto

user28409 Points 6460

Un raccourci pour comprendre yield

Lorsque vous voyez une fonction avec yield déclarations, appliquez cette astuce simple pour comprendre ce qui va se passer :

  1. Insérer une ligne result = [] au début de la fonction.
  2. Remplacer chaque yield expr avec result.append(expr) .
  3. Insérer une ligne return result au bas de la fonction.
  4. Yay - pas plus yield déclarations ! Lire et comprendre le code.
  5. Comparez la fonction à la définition originale.

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 sensiblement différent de ce qui se passe dans l'approche basée sur les listes. Dans de nombreux cas, l'approche par le rendement sera beaucoup plus efficace en termes de mémoire et plus rapide aussi. Dans d'autres cas, cette astuce vous bloquera dans une boucle infinie, même si la fonction originale fonctionne parfaitement. Lisez la suite pour en savoir plus...

Ne confondez pas vos Iterables, Iterators, et Generators

Tout d'abord, le protocole d'itération - quand vous écrivez

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

Python effectue les deux étapes suivantes :

  1. Obtient un itérateur pour mylist :

    Appelez iter(mylist) -> ceci renvoie un objet avec un next() (ou __next__() dans Python 3).

    [C'est l'étape dont la plupart des gens oublient de vous parler].

  2. Utilise l'itérateur pour boucler sur les éléments :

    Continuez à appeler le next() sur l'itérateur renvoyé à l'étape 1. La valeur de retour de next() est affecté à x et le corps de la boucle est exécuté. Si une exception StopIteration est soulevé de l'intérieur next() cela signifie qu'il n'y a plus de valeurs dans l'itérateur et que la boucle est terminée.

En réalité, Python exécute les deux étapes ci-dessus quand il le souhaite. boucle sur le contenu d'un objet - il peut donc s'agir d'une boucle for, mais aussi d'un code tel que otherlist.extend(mylist) (où otherlist est une liste Python).

Ici mylist est un itérable car elle implémente le protocole des itérateurs. Dans une classe définie par l'utilisateur, vous pouvez implémenter le protocole d'itérateur __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 un next() méthode. Il est possible de mettre en œuvre les deux méthodes __iter__() et next() sur la même classe, et ont __iter__() retourner self . Cela fonctionne dans des cas simples, mais pas lorsque vous voulez que deux itérateurs bouclent sur le même objet en même temps.

C'est donc le protocole des itérateurs, de nombreux objets mettent en œuvre ce protocole :

  1. Listes, dictionnaires, tuples, ensembles et fichiers intégrés.
  2. Les classes définies par l'utilisateur qui implémentent __iter__() .
  3. Générateurs.

Notez qu'un for ne sait pas à quel type d'objet elle a affaire - elle suit simplement le protocole de l'itérateur, et est heureuse d'obtenir élément après élément en appelant next() . Les listes intégrées renvoient leurs éléments un par un, les dictionnaires renvoient l'ensemble des éléments de la liste. clés un par un, les fichiers retournent le lignes un par un, etc. Et le retour des générateurs... et bien c'est là que yield vient :

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 les déclarations dans f123() seule la première serait exécutée, et la fonction se terminerait. Mais f123() n'est pas une fonction ordinaire. Quand f123() est appelé, il n'est pas retourner n'importe quelle valeur dans les déclarations yield ! Elle renvoie un objet générateur. En outre, la fonction ne se termine pas vraiment - elle passe dans un état suspendu. Lorsque la fonction for tente de boucler sur l'objet générateur, la fonction reprend son état suspendu à la ligne suivante, après la balise yield qu'il a précédemment retourné, exécute la ligne de code suivante, dans ce cas, un yield et le renvoie comme élément suivant. Cela se produit jusqu'à ce que la fonction sorte, auquel cas le générateur lève la case StopIteration et la boucle se termine.

Ainsi, l'objet générateur est en quelque sorte un adaptateur : d'un côté, il expose le protocole des itérateurs, en exposant l'objet __iter__() et next() pour garder le for boucle heureuse. À l'autre bout, cependant, il exécute la fonction juste assez pour obtenir la valeur suivante, et la remet en mode suspendu.

Pourquoi utiliser des générateurs ?

En général, vous pouvez écrire du code qui n'utilise pas de générateurs mais qui met en œuvre la même logique. Une option consiste à utiliser l'astuce de la liste temporaire dont j'ai parlé précédemment. Cela ne fonctionnera pas dans tous les cas, par exemple si vous avez des boucles infinies, ou si vous avez une liste très longue, ce qui peut entraîner une utilisation inefficace de la mémoire. L'autre approche est d'implémenter une nouvelle classe itérable SomethingIter qui garde l'état dans les membres d'instance et exécute l'étape logique suivante dans ses membres d'instance. next() (ou __next__() dans la méthode Python 3). Selon la logique, le code à l'intérieur de la méthode next() peut finir par paraître très complexe et être sujette à des bogues. Ici, les générateurs fournissent une solution propre et facile.

28 votes

"Lorsque vous voyez une fonction avec des déclarations de rendement, appliquez cette astuce simple pour comprendre ce qui va se passer". N'est-ce pas ignorer complètement le fait que vous pouvez send dans un générateur, ce qui est une grande partie de l'intérêt des générateurs ?

12 votes

"ça pourrait être une boucle for, mais ça pourrait aussi être du code comme otherlist.extend(mylist) "-> Ceci est incorrect. extend() modifie la liste en place et ne retourne pas un itérable. En essayant de boucler sur otherlist.extend(mylist) échouera avec un TypeError parce que extend() retourne implicitement None et vous ne pouvez pas boucler sur None .

8 votes

@pedro Vous avez mal compris cette phrase. Elle signifie que python effectue les deux étapes mentionnées sur mylist (pas sur otherlist ) lors de l'exécution de otherlist.extend(mylist) .

635voto

Jason Baker Points 56682

Pensez-y de cette façon :

Un itérateur est juste un terme fantaisiste pour un objet qui a un next() méthode. Donc une fonction de rendement finit par être quelque chose comme ça :

Version originale :

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

for i in some_function():
    print i

C'est en gros ce que fait l'interpréteur Python 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 en savoir plus sur ce qui se passe dans les coulisses, l for peut être réécrite comme suit :

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

Est-ce que ça a plus de sens ou est-ce que ça vous embrouille encore plus ? :)

Je dois noter que cette est une simplification excessive à des fins d'illustration :)

1 votes

__getitem__ pourrait être défini à la place de __iter__ . Par exemple : class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i) Il imprimera : 0, 10, 20, ..., 90

25 votes

J'ai essayé cet exemple dans Python 3.6 et si je crée iterator = some_function() la variable iterator n'a pas de fonction appelée next() plus, mais seulement un __next__() fonction. J'ai pensé que je devais le mentionner.

0 votes

Où se trouve le for l'implémentation de la boucle que vous avez écrite appelle le __iter__ méthode de iterator , l'instance instanciée de it ?

513voto

ninjagecko Points 25709

Le site yield Le mot clé se réduit à deux faits simples :

  1. Si le compilateur détecte le yield mot-clé partout à l'intérieur d'une fonction, cette fonction ne retourne plus via la fonction return déclaration. Au lieu de cela, il immédiatement renvoie un objet "liste en attente" paresseux appelé un générateur
  2. Un générateur est itérable. Qu'est-ce qu'un itérable ? C'est tout comme list ou set ou range ou dictée, 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 et incrémentale. et yield Les instructions vous permettent d'utiliser la notation de fonction pour programmer les valeurs de la liste. que le générateur devrait cracher de manière incrémentielle.

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 ressemble à l'outil Python range . 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 à renvoyer immédiatement les valeurs en attente, vous pouvez le passer dans le champ list() (comme vous le feriez pour n'importe quel itérable) :

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

Comparaison de l'exemple avec le "simple renvoi d'une liste".

L'exemple ci-dessus peut être considéré comme la création d'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]

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


Comment utiliser les générateurs

Un itérable est la dernière partie d'une compréhension de liste, et tous les générateurs sont itérables, donc ils sont souvent utilisés comme ça :

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

Pour vous familiariser avec les générateurs, vous pouvez jouer avec les paramètres suivants itertools (veillez à utiliser le module chain.from_iterable plutôt que chain lorsque cela est justifié). Par exemple, vous pouvez même utiliser des générateurs pour implémenter des listes paresseuses infiniment longues comme itertools.count() . Vous pourriez mettre en œuvre votre propre def enumerate(iterable): zip(count(), iterable) ou, alternativement, avec le bouton yield dans une boucle de type "while".

Remarque : les générateurs peuvent en fait être utilisés pour bien d'autres choses, telles que mise en œuvre des coroutines ou la programmation non-déterministe ou d'autres choses élégantes. Cependant, le point de vue des "listes paresseuses" que je présente ici est l'utilisation la plus courante que vous trouverez.


Les coulisses

C'est ainsi que fonctionne le "protocole d'itération Python". C'est-à-dire, ce qui se passe lorsque vous faites list(makeRange(5)) . C'est ce que j'ai décrit précédemment comme une "liste paresseuse et 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 les 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 implémenter des choses fantaisistes, généralement au détriment de la lisibilité, donc essayez d'éviter de faire cela...


Minutiae

Normalement, la plupart des gens ne se soucient pas des distinctions suivantes et souhaitent probablement arrêter leur lecture 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 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 des fonctions).

Lorsque vous demandez un itérateur à partir d'une liste, un nouvel itérateur est créé. Cependant, lorsque vous demandez un itérateur à un itérateur (ce que vous ferez rarement), il vous donne simplement une copie de lui-même.

Ainsi, dans le cas peu probable où vous échouez à faire quelque chose comme ça...

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

... alors rappelez-vous qu'un générateur est une itérateur c'est-à-dire qu'il est à usage unique. Si vous voulez le 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-la dans une variable x = list(myRange(5)) . Ceux qui ont absolument besoin de cloner un générateur (par exemple, ceux qui font de la métaprogrammation terriblement bidouillée) peuvent utiliser itertools.tee si cela est absolument nécessaire, puisque l'itérateur copiable Python PEP La proposition de normes a été reportée.

122voto

Robert Rossney Points 43767

J'ai l'impression de poster un lien vers cette présentation tous les jours : La présentation de David M. Beazly Trucs de générateurs pour les programmeurs de systèmes . Si vous êtes un programmeur Python et que vous n'êtes pas très familier avec les générateurs, vous devriez lire ceci. Il s'agit d'une explication très claire de ce que sont les générateurs, de leur fonctionnement, de ce que fait l'instruction yield, et il répond à la question "Voulez-vous vraiment jouer avec cette obscure fonctionnalité du langage ?".

ALERTE SPOILER. La réponse est : oui. Oui, vous le savez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X