web-dev-qa-db-fra.com

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? Quelques exemples d'utilisation de chaque cas seraient utiles.

470

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

Chaque générateur est un itérateur, mais pas l'inverse. Un générateur est construit en appelant une fonction qui possède une ou plusieurs expressions yield (instructions yield, dans Python 2.5 et versions antérieures), et correspond à la définition du paragraphe précédent. d'un iterator.

Vous souhaiterez peut-être utiliser un itérateur personnalisé, plutôt qu'un générateur, lorsque vous avez besoin d'une classe ayant un comportement de maintien d'état relativement complexe, ou d'exposer d'autres méthodes que next (et __iter__ et __init__). Le plus souvent, un générateur (parfois, pour des besoins suffisamment simples, un générateur expression) est suffisant et plus simple à coder car la maintenance de l'état (dans des limites raisonnables) est "faite pour vous" par le cadre être suspendu et repris.

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 équivalent (genexp)

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

prendrait plus de code pour construire 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 3
       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 la classe Squares, vous pouvez facilement proposer des méthodes supplémentaires, c.-à-d.

    def current(self):
       return self.start

si vous avez réellement besoin de fonctionnalités supplémentaires dans votre application.

479
Alex Martelli

Quelle est la différence entre les itérateurs et les générateurs? Quelques exemples de cas où vous utiliseriez chaque cas seraient utiles.

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

Une fonction avec un rendement en 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 une exposition plus détaillée et des exemples, continuez à lire.

Un générateur est un itérateur

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 manières. Une méthode très courante et simple consiste à utiliser une fonction.

Plus précisément, une fonction contenant un rendement 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 itérateur:

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

Un itérateur est un itérable

Un itérateur est un itérable,

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

qui nécessite une méthode __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 n-uplets, les listes, les dictionnaires, les ensembles, les ensembles gelés, les chaînes, les chaînes d'octets, les tableaux d'octets, les plages et les visualisations de mémoire intégrés:

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

Les itérateurs nécessitent une méthode next ou __next__

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 à partir des objets intégrés (ou des objets personnalisés) avec la fonction iter:

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

La méthode __iter__ est appelée lorsque vous essayez d'utiliser un objet avec une boucle for. Ensuite, la méthode __next__ est appelée sur l'objet itérateur pour extraire chaque élément de la boucle. L'itérateur soulève StopIteration lorsque vous l'avez épuisé et il ne peut pas être réutilisé à ce stade.

De la documentation

À partir de la section Types de générateur de la section Types d'itérateur des types intégrés documentation :

Les générateurs de Python constituent un moyen pratique d'implémenter le protocole d'itérateur. Si la méthode __iter__() d'un objet conteneur est implémentée en tant que générateur, elle renvoie automatiquement un objet itérateur (techniquement, un objet générateur) fournissant les méthodes __iter__() et next() [__next__() dans Python 3]. Vous trouverez plus d'informations sur les générateurs dans la documentation de l'expression de rendement.

(Soulignement ajouté.)

Nous apprenons donc que les générateurs sont un type (pratique) d’Iterator.

Exemple d'objets Itérateur

Vous pouvez créer un objet qui implémente le protocole Iterator en créant ou en développant 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 le faire:

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

Ou peut-être plus simplement, une expression de générateur (fonctionne de la même manière que la liste de compréhensions):

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 le protocole Iterator directement lorsque vous avez besoin d'étendre un objet Python en tant qu'objet pouvant être itéré.

Toutefois, dans la grande majorité des cas, il est préférable d’utiliser yield pour définir une fonction qui renvoie un générateur d’itérateur ou de considérer des expressions de générateur.

Enfin, notez que les générateurs offrent encore plus de fonctionnalités que les routines. J'explique les générateurs, ainsi que la déclaration yield, de manière plus détaillée dans ma réponse à "Que fait le mot-clé" rendement "?".

112
Aaron Hall

Itérateurs:

Les itérateurs sont des objets qui utilisent la méthode next() pour obtenir la valeur suivante de la séquence.

Générateurs:

Un générateur est une fonction qui produit ou produit une séquence de valeurs en utilisant la méthode yield.

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

Lorsqu'une fonction génératrice est appelée, elle retourne un objet générateur sans même commencer l'exécution de la fonction. Lorsque la méthode next() est appelée pour la première fois, la fonction commence à s'exécuter jusqu'à atteindre l'instruction de rendement qui renvoie la valeur renvoyée. Le rendement garde la trace de, c’est-à-dire qu’il se souvient de la dernière exécution. Et le second appel next() continue à partir de la valeur précédente.

L'exemple suivant illustre l'interaction entre le rendement et l'appel à la méthode suivante sur l'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
>>>
31
user966588

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

Les fonctions génératrices sont des fonctions ordinaires définies à l'aide de yield au lieu de return. Lorsqu’elle est appelée, une fonction génératrice renvoie un objet générateur , qui est une sorte d’itérateur - elle a une méthode next(). Lorsque vous appelez next(), la valeur suivante générée par la fonction du générateur est renvoyée.

La fonction ou l'objet peut être appelé le "générateur" en fonction du document source Python que vous avez lu. Le glossaire Python indique les fonctions du générateur, tandis que le wiki Python implique des objets générateurs. Le tutoriel Python parvient remarquablement à impliquer les deux utilisations dans l'espace de trois phrases:

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

Les deux premières phrases identifient les générateurs avec des fonctions génératrices, tandis que la troisième phrase les identifie avec des objets générateurs.

Malgré toute cette confusion, on peut rechercher le référence du langage Python pour le mot clair et final:

L'expression de rendement n'est utilisée que lors de la définition d'une fonction génératrice et ne peut être utilisée que dans le corps d'une définition de fonction. L'utilisation d'une expression de rendement dans une définition de fonction suffit pour que cette définition crée une fonction génératrice 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 du générateur.

Ainsi, dans un usage formel et précis, "générateur" signifie un objet générateur générateur, et non une fonction génératrice.

Les références ci-dessus concernent Python 2 mais référence du langage Python dit la même chose. Cependant, le glossaire Python indique 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ù la signification voulue n’est pas claire, l’utilisation des termes complets évite toute ambiguïté.

18
Paul

Tout le monde a une réponse très gentille et verbeuse avec des exemples et je l’apprécie vraiment. Je voulais juste donner une courte réponse à quelques personnes qui ne sont pas encore très claires sur le plan conceptuel:

Si vous créez votre propre itérateur, il est un peu impliqué - vous devez créer une classe et au moins implémenter l'itérateur et les méthodes suivantes. Mais que se passe-t-il si vous ne voulez pas traverser cette galère et que vous voulez créer rapidement un itérateur? Heureusement, Python fournit un moyen rapide de définir un itérateur. Tout ce que vous avez à faire est de définir une fonction avec au moins 1 appel à renoncer. Désormais, lorsque vous appelez cette fonction, elle renvoie "quelque chose" qui agira comme un itérateur (vous pouvez appeler la méthode next et utiliser dans une boucle). Ce quelque chose a un nom dans Python appelé Generator

J'espère que cela clarifie un peu.

9
Heapify

Les réponses précédentes manquaient cet ajout: un générateur utilise une méthode close, contrairement aux itérateurs classiques. La méthode close déclenche une exception StopIteration dans le générateur, qui peut être interceptée dans une clause finally de cet itérateur, pour pouvoir effectuer un nettoyage. Cette abstraction le rend plus utilisable dans le grand que les simples itérateurs. On peut fermer un générateur comme on pourrait fermer un fichier, sans avoir à se soucier de ce qui se trouve en dessous.

Cela dit, ma réponse personnelle à la première question serait la suivante: itératable utilise une méthode ___iter___ uniquement, les itérateurs types ont une méthode ___next___ uniquement, les générateurs ont à la fois un ___iter___ et un ___next___ et un close supplémentaire.

Pour la deuxième question, ma réponse personnelle serait: dans une interface publique, j’ai tendance à beaucoup favoriser les générateurs, car elle est plus résistante: la méthode close permet une plus grande composabilité avec _yield from_. Localement, je peux utiliser des itérateurs, mais seulement s’il s’agit d’une structure plate et simple (les itérateurs ne composent pas facilement) et s’il ya des raisons de croire que la séquence est assez courte, en particulier si elle peut être arrêtée avant d’atteindre la fin. J'ai tendance à considérer les itérateurs comme une primitive de bas niveau, à l'exception des littéraux.

Pour les questions de contrôle de flux, les générateurs sont un concept aussi important que les promesses: les deux sont abstraits et composables.

6
Hibou57

Fonction de générateur, Objet de générateur, Générateur:

Un fonction du générateur est identique à une fonction normale dans Python mais il contient une ou plusieurs instructions yield. Fonctions de générateur est un excellent outil pour créer des objets Iterator aussi facile que possible. Le Iterator objet returend par la fonction de générateur est également appelé objet Generator ou Generator.

Dans cet exemple, j'ai créé une fonction Generator qui renvoie un objet Generator <generator object fib at 0x01342480>. Tout comme les autres itérateurs, les objets Generator peuvent être utilisés dans une boucle for ou avec la fonction intégrée next() qui renvoie la valeur suivante du générateur.

def fib(max):
    a, b = 0, 1
    for i in range(max):
        yield a
        a, b = b, a + b
print(fib(10))             #<generator object fib at 0x01342480>

for i in fib(10):
    print(i)               # 0 1 1 2 3 5 8 13 21 34


print(next(myfib))         #0
print(next(myfib))         #1
print(next(myfib))         #1
print(next(myfib))         #2

ne fonction de générateur est donc le moyen le plus simple de créer un objet Iterator.

Itérateur:

Chaque objet générateur est un itérateur mais pas l'inverse. Un objet itérateur personnalisé peut être créé si sa classe implémente la méthode __iter__ et __next__ (également appelé protocole itérateur).

Cependant, il est beaucoup plus facile d’utiliser des générateurs pour créer itérateurs car ils simplifient leur création, mais un itérateur personnalisé vous donne plus de liberté et vous pouvez également implémenter d’autres méthodes en fonction de vos besoins, comme indiqué ci-dessous. exemple.

class Fib:
    def __init__(self,max):
        self.current=0
        self.next=1
        self.max=max
        self.count=0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count>self.max:
            raise StopIteration
        else:
            self.current,self.next=self.next,(self.current+self.next)
            self.count+=1
            return self.next-self.current

    def __str__(self):
        return "Generator object"

itobj=Fib(4)
print(itobj)               #Generator object

for i in Fib(4):  
    print(i)               #0 1 1 2

print(next(itobj))         #0
print(next(itobj))         #1
print(next(itobj))         #1
6
N Randhawa

Vous pouvez comparer les deux approches pour les mêmes données:

def myGeneratorList(n):
    for i in range(n):
        yield i

def myIterableList(n):
    ll = n*[None]
    for i in range(n):
        ll[i] = i
    return ll

# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in Zip(ll1, ll2):
    print("{} {}".format(i1, i2))

# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

De plus, si vous vérifiez l'empreinte mémoire, le générateur utilise beaucoup moins de mémoire car il n'a pas besoin de stocker toutes les valeurs en mémoire en même temps.

4
tashuhka

Exemples de Ned Batchelder hautement recommandé pour les itérateurs et les générateurs

Une méthode sans générateurs qui font quelque chose pour les nombres pairs

def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them

en utilisant un générateur

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n
  • Nous n'avons besoin d'aucune liste ni d'une instruction return
  • Efficace pour les flux de grande taille/longueur infinie ... il marche juste et donne la valeur

L'appel de la méthode evens (générateur) est comme d'habitude

num = [...]
for n in evens(num):
   do_smth(n)
  • Générateur également utilisé pour rompre la double boucle

Itérateur

Un livre rempli de pages est un itérable , un signet est un itérateur

et ce signet n'a rien à faire à part déplacer next

litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration  (Exception) as we got end of the iterator

Pour utiliser Generator ... nous avons besoin d'une fonction

Pour utiliser Iterator ... nous avons besoin de next et de iter

Comme cela a été dit:

Le générateur est un itérateur

Le tout avantage d'Iterator:

Mémoriser un élément par temps

1
Marwan Mostafa