743 votes

Différence entre les générateurs et les itérateurs de Python

Quelle est la différence entre les itérateurs et les générateurs ? Il serait utile de donner quelques exemples d'utilisation de chaque cas.

761voto

Alex Martelli Points 330805

iterator est un concept plus général : tout objet dont la classe a un __next__ méthode ( next dans Python 2) et un __iter__ qui fait return self .

Tout générateur est un itérateur, mais pas l'inverse. Un générateur est construit en appelant une fonction qui a un ou plusieurs yield expressions ( yield dans Python 2.5 et antérieurs), et est un objet qui répond à la définition du paragraphe précédent d'un iterator .

Vous pouvez utiliser un itérateur personnalisé, plutôt qu'un générateur, lorsque vous avez besoin d'une classe dont le comportement de maintien d'état est quelque peu complexe, ou lorsque vous voulez exposer d'autres méthodes en dehors de __next__ (et __iter__ y __init__ ). Le plus souvent, un générateur (parfois, pour des besoins suffisamment simples, un groupe électrogène expression ) est suffisant, et c'est plus simple à coder car le maintien de l'état (dans des limites raisonnables) est essentiellement "fait pour vous" par la suspension et la reprise de la trame.

Par exemple, un générateur tel que :

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

ou l'expression du générateur équivalente (genexp)

generator = (i*i for i in range(a, b))

demanderait plus de code pour être construit comme un itérateur personnalisé :

class Squares(object):
    def __init__(self, start, stop):
       self.start = start
       self.stop = stop
    def __iter__(self): return self
    def __next__(self): # next in Python 2
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

Mais, bien sûr, avec de la classe Squares vous pourriez facilement proposer des méthodes supplémentaires, c'est-à-dire

    def current(self):
       return self.start

si vous avez réellement besoin d'une telle fonctionnalité supplémentaire dans votre application.

197voto

Aaron Hall Points 7381

Quelle est la différence entre les itérateurs et les générateurs ? Il serait utile de donner quelques exemples d'utilisation de chaque cas.

En résumé : les itérateurs sont des objets qui ont un __iter__ et un __next__ ( next dans la méthode Python 2). Les générateurs offrent un moyen simple et intégré de créer des instances d'itérateurs.

Une fonction avec yield est toujours une fonction qui, lorsqu'elle est appelée, renvoie une instance d'un objet générateur :

def a_function():
    "when called, returns generator object"
    yield

Une expression de générateur renvoie également un générateur :

a_generator = (i for i in range(0))

Pour un exposé plus approfondi et des exemples, poursuivez votre lecture.

Un générateur es un Iterator

Plus précisément, le générateur est un sous-type d'itérateur.

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

Nous pouvons créer un générateur de plusieurs façons. Une façon très courante et simple de le faire est d'utiliser une fonction.

Plus précisément, une fonction avec yield est une fonction qui, lorsqu'elle est appelée, renvoie un générateur :

>>> def a_function():
        "just a function definition with yield in it"
        yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function()  # when called
>>> type(a_generator)           # returns a generator
<class 'generator'>

Et un générateur, encore une fois, est un Iterator :

>>> isinstance(a_generator, collections.Iterator)
True

Un Iterator es un Iterable

Un Iterator est un Iterable,

>>> issubclass(collections.Iterator, collections.Iterable)
True

qui nécessite un __iter__ qui renvoie un Iterator :

>>> collections.Iterable()
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__

Quelques exemples d'itérables sont les tuples intégrés, les listes, les dictionnaires, les ensembles, les ensembles figés, les chaînes de caractères, les chaînes d'octets, les tableaux d'octets, les plages et les vues mémoire :

>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

Itérateurs exiger a next o __next__ méthode

Dans Python 2 :

>>> collections.Iterator()
Traceback (most recent call last):
  File "<pyshell#80>", line 1, in <module>
    collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next

Et dans Python 3 :

>>> collections.Iterator()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__

Nous pouvons obtenir les itérateurs des objets intégrés (ou des objets personnalisés) avec la fonction iter fonction :

>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

El __iter__ est appelée lorsque vous tentez d'utiliser un objet avec une boucle for. Ensuite, la méthode __next__ est appelée sur l'objet itérateur pour récupérer chaque élément de la boucle. L'itérateur lève StopIteration lorsque vous l'avez épuisé, et il ne peut plus être réutilisé à ce moment-là.

Extrait de la documentation

Dans la section Types de générateurs de la section Types d'itérateurs de la section Types intégrés. documentation :

Python Les générateurs fournissent un moyen pratique de mettre en œuvre le protocole des itérateurs. Si l'objet conteneur __iter__() est implémentée en tant que générateur, elle renverra automatiquement un objet itérateur (techniquement, un objet générateur) fournissant à la méthode __iter__() y next() [ __next__() dans les méthodes Python 3]. Vous trouverez de plus amples informations sur les générateurs dans la documentation relative à l'expression yield.

(C'est nous qui soulignons.)

Nous apprenons donc que les générateurs sont un type (pratique) d'itérateur.

Exemples d'objets d'itération

Vous pouvez créer un objet qui implémente le protocole Iterator en créant ou en étendant votre propre objet.

class Yes(collections.Iterator):

    def __init__(self, stop):
        self.x = 0
        self.stop = stop

    def __iter__(self):
        return self

    def next(self):
        if self.x < self.stop:
            self.x += 1
            return 'yes'
        else:
            # Iterators must raise when done, else considered broken
            raise StopIteration

    __next__ = next # Python 3 compatibility

Mais il est plus facile d'utiliser simplement un générateur pour faire cela :

def yes(stop):
    for _ in range(stop):
        yield 'yes'

Ou, peut-être plus simplement, une expression génératrice (fonctionne de manière similaire aux compréhensions de listes) :

yes_expr = ('yes' for _ in range(stop))

Ils peuvent tous être utilisés de la même manière :

>>> stop = 4             
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop), 
                             ('yes' for _ in range(stop))):
...     print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...     
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes

Conclusion

Vous pouvez utiliser directement le protocole Iterator lorsque vous devez étendre un objet Python en tant qu'objet sur lequel il est possible d'effectuer une itération.

Cependant, dans la grande majorité des cas, il est préférable d'utiliser yield pour définir une fonction qui renvoie un Iterator Generator ou considère des Expressions Generator.

Enfin, notez que les générateurs offrent encore plus de fonctionnalités que les coroutines. J'explique que les générateurs, ainsi que les yield en profondeur sur ma réponse à la question "Que fait le mot clé "yield" ?".

55voto

daa Points 2202

Iterators :

Iterator sont des objets qui utilisent next() pour obtenir la valeur suivante de la séquence.

Générateurs :

Un générateur est une fonction qui produit ou donne une séquence de valeurs à l'aide des éléments suivants yield méthode.

Chaque next() appel de méthode sur un objet générateur (par ex : f comme dans l'exemple ci-dessous) renvoyée par la fonction du générateur (par ex : foo() dans l'exemple ci-dessous), génère la valeur suivante dans la séquence.

Lorsqu'une fonction de générateur est appelée, elle renvoie un objet générateur sans même commencer l'exécution de la fonction. Lorsque next() est appelée pour la première fois, la fonction commence à s'exécuter jusqu'à ce qu'elle atteigne l'instruction yield qui renvoie la valeur cédée. L'instruction yield garde la trace, c'est-à-dire qu'elle se souvient de la dernière exécution. Et la deuxième next() l'appel se poursuit à partir de la valeur précédente.

L'exemple suivant démontre l'interaction entre le yield et l'appel à la méthode suivante sur un objet générateur.

>>> def foo():
...     print "begin"
...     for i in range(3):
...         print "before yield", i
...         yield i
...         print "after yield", i
...     print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0            # Control is in for loop
0
>>> f.next()
after yield 0             
before yield 1            # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

6 votes

Pour votre information, le rendement n'est pas une méthode, c'est un mot-clé.

0 votes

Au lieu de f.next() il devrait l'être next(f)

44voto

Paul Points 1537

Ajout d'une réponse car aucune des réponses existantes ne traite spécifiquement de la confusion dans la littérature officielle.

Fonctions du générateur sont des fonctions ordinaires définies par yield au lieu de return . Lorsqu'elle est appelée, une fonction de générateur renvoie un objet générateur qui est une sorte d'itérateur - il possède un next() méthode. Lorsque vous appelez next() la valeur suivante produite par la fonction de générateur est retournée.

La fonction ou l'objet peut être appelé "générateur" selon le document source Python que vous lisez. Le site Glossaire Python dit les fonctions de générateur, tandis que le Python wiki implique des objets générateurs. Le site Tutoriel Python réussit remarquablement à impliquer les deux usages en l'espace de trois phrases :

Les générateurs sont un outil simple et puissant pour créer des itérateurs. Ils s'écrivent comme des fonctions ordinaires, mais utilisent l'instruction yield chaque fois qu'ils veulent renvoyer des données. Chaque fois que la fonction next() est appelée, le générateur reprend là où il s'est arrêté (il se souvient de toutes les valeurs des données et de la dernière instruction exécutée).

Les deux premières phrases identifient les générateurs aux fonctions de générateur, tandis que la troisième phrase les identifie aux objets de générateur.

En dépit de toute cette confusion, on peut chercher le Référence du langage Python pour le mot clair et définitif :

L'expression yield n'est utilisée que lors de la définition d'une fonction de générateur, et ne peut être utilisée que dans le corps d'une définition de fonction. Il suffit d'utiliser une expression yield dans une définition de fonction pour que cette définition crée une fonction de générateur au lieu d'une fonction normale.

Lorsqu'une fonction de générateur est appelée, elle renvoie un itérateur appelé générateur. Ce générateur contrôle ensuite l'exécution d'une fonction de générateur.

Donc, dans l'usage formel et précis, "générateur" non qualifié signifie objet générateur, et non fonction générateur.

Les références ci-dessus sont pour Python 2 mais Référence du langage Python 3 dit la même chose. Cependant, le Glossaire Python 3 déclare que

générateur ... Fait généralement référence à une fonction de générateur, mais peut faire référence à un itérateur de générateur dans certains contextes. Dans les cas où le sens voulu n'est pas clair, l'utilisation des termes complets évite toute ambiguïté.

31voto

Heapify Points 755

Tout le monde a répondu de manière très claire et détaillée en donnant des exemples, et j'apprécie vraiment cela. Je voulais juste donner une réponse courte de quelques lignes pour les personnes qui ne sont pas encore tout à fait claires sur le plan conceptuel :

Si vous créez votre propre itérateur, c'est un peu compliqué. créer une classe et au moins implémenter les méthodes iter et next. Mais que se passe-t-il si vous ne voulez pas passer par cette étape fastidieuse et que vous souhaitez créer rapidement un itérateur ? Heureusement, Python offre un moyen rapide de définir un itérateur. Tout ce que vous avez à faire est de définir une fonction avec au moins un appel à yield et maintenant, lorsque vous appelez cette fonction, elle retournera " algo "qui agira comme un itérateur (vous pouvez appeler la méthode next et l'utiliser dans une boucle for). Ce algo a un nom en Python appelé Generator

J'espère que cela clarifie un peu les choses.

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