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

Quelle est la différence entre les itérateurs et les générateurs? Quelques exemples pour savoir quand vous utiliserez chaque cas seraient utiles.

388
demandé sur Aaron Hall 2010-05-06 01:14:08

8 réponses

iterator est un concept plus général: tout objet dont la classe a une méthode next ( __next__ en 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 a une ou plusieurs expressions yield ( yield , en python 2.5 et versions précédentes), et est un objet qui répond à la définition d'un iterator donnée dans le paragraphe précédent .

vous pouvez utiliser un itérateur personnalisé, plutôt qu'un générateur, quand vous avez besoin d'une classe avec un comportement de maintien d'état quelque peu complexe, ou que vous voulez 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 il est plus simple de coder parce que l'entretien de l'état (dans des limites raisonnables) est essentiellement " fait pour vous" par le cadre de la suspension et de la reprise.

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'équivalent, générateur d'expression (genexp)

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

aurait besoin de plus de code pour construire en tant qu'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):
       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 pourriez facilement offrir des méthodes supplémentaires ,i.e.

    def current(self):
       return self.start

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

413
répondu Alex Martelli 2017-02-13 10:54:50

Quelle est la différence entre les itérateurs et les générateurs? Quelques exemples pour savoir quand vous utiliserez chaque cas seraient utiles.

en résumé: les itérateurs sont des objets qui ont une méthode __iter__ et __next__ ( next en Python 2). Les générateurs fournissent un moyen facile et intégré de créer des instances D'itérateurs.

une fonction avec un rendement en elle est encore une fonction, qui, lorsqu'elle est appelée, retourne une instance d'un objet générateur:

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

Un générateur d'expression renvoie également un générateur de:

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

pour une présentation plus détaillée et des exemples, continuez à lire.

Un Générateur de 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 façons. Un très commune et façon simple de le faire est avec une fonction.

spécifiquement, une fonction avec un rendement en elle 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érateur,

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

qui exige une méthode __iter__ qui renvoie un itérateur:

>>> 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 de itérables sont les tuples intégrés, les listes, les dictionnaires, les ensembles, les ensembles gelés, les chaînes, les chaînes de byte, les tableaux de byte, les gammes et les vues de mémoire:

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

Itérateurs exiger un next ou __next__ méthode

En 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 en 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 tentez d'utiliser un objet avec une boucle for. Ensuite, la méthode __next__ est appelée sur l'objet iterator pour obtenir chaque item pour la boucle. L'itérateur soulève StopIteration quand vous avez épuisé, et il ne peut être réutilisé à ce point.

de la documentation

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

Python sont un moyen pratique d'implémenter le protocole iterator. si la méthode __iter__() d'un objet conteneur est implémentée en tant que Générateur, il retournera automatiquement un objet itérateur (techniquement, un objet générateur) fournissant le __iter__() et next() [ __next__() en Python 3] méthodes. Plus d'informations sur les générateurs peuvent être trouvées dans la documentation pour l'expression de rendement.

(non souligné dans l'original.)

ainsi, nous apprenons que les générateurs sont un type (pratique) D'itérateur.

Exemple D'Objets Itérateurs

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 simple, une Expression génératrice (fonctionne de la même façon que des interprétations de liste):

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 objet que l'on peut parcourir.

cependant, dans la grande majorité des cas, vous êtes le mieux adapté à utiliser yield pour définir une fonction qui renvoie un itérateur de géné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 coroutines. j'explique générateurs, avec le yield déclaration, en profondeur sur ma réponse à "Que fait le" rendement " mot clé?".

72
répondu Aaron Hall 2018-09-21 16:21:31

itérateurs:

Itérateur sont des objets qui utilise next() méthode 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 l'objet générateur (pour ex: f comme dans l'exemple ci-dessous) retourné par la fonction génératrice(pour ex: foo() fonction ci-dessous) exemple), génère la valeur suivante dans la séquence.

quand 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 la méthode 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 obtenue. Le rendement garde la trace de i.e. se souvient de la dernière exécution. Et le deuxième appel next() continue à partir de la valeur précédente.

le suivant exemple démontre l'interaction entre le rendement et l'appel à la méthode suivante sur l'objet du 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
>>>
27
répondu 2015-01-27 08:15:45

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

fonctions de générateur sont des fonctions ordinaires définies en utilisant yield au lieu de return . Lorsqu'on l'appelle, une fonction de générateur renvoie un " generator object , qui est une sorte d'itérateur - il a une méthode next() . Lorsque vous appelez next() , la valeur suivante fournie par la fonction du générateur est retournée.

la fonction ou l'objet peut être appelé le" générateur " selon le document source Python que vous lisez. Le Python glossary indique des fonctions de générateur, tandis que le Python wiki implique des objets de générateur. Le tutoriel Python parvient remarquablement à impliquer les deux usages dans l'espace de trois phrases:

Les générateurs

sont un outil simple et puissant pour créer des itérateurs. Ils sont écrits comme des fonctions régulières mais utilisent l'instruction yield chaque fois qu'ils veulent retourner des données. Chaque fois que next() est appelé, le générateur reprend là où il s'est arrêté (il se souvient de toutes les valeurs de données et quelle instruction a été exécutée en dernier).

les deux premières phrases identifient les générateurs avec des fonctions de générateur, alors que la troisième phrase, les identifie avec les objets générateur.

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

l'expression de rendement n'est utilisée que pour définir une fonction de générateur, 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 est suffisante pour que cette définition crée un générateur de fonction au lieu d'une fonction normale.

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

" generator "UNR qualified means generator object, not generator function.

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

générateur ... Renvoie habituellement à une fonction de générateur, mais peut renvoyer à un itérateur de générateur dans certains contextes. Dans les cas où la signification prévue n'est pas claire, l'utilisation de tous les Termes évite toute ambiguïté.

14
répondu Paul 2016-08-27 13:44:15

tout le monde a une réponse très agréable et verbeuse avec des exemples et je l'apprécie vraiment. Je voulais juste donner une réponse courte de quelques lignes pour les gens qui ne sont pas encore tout à fait clair conceptuellement:

si vous créez votre propre itérateur, il est un peu impliqué - vous avez pour créer une classe et au moins implémenter l'iter et les méthodes suivantes. Mais que faire si vous ne voulez pas passer par ce tracas et vous voulez créer rapidement un itérateur. Heureusement, Python fournit un raccourci pour définir un itérateur. Tout ce que vous devez faire est de définir une fonction avec au moins un appel à yield et maintenant quand vous appelez cette fonction il retournera " quelque chose " qui agira comme un itérateur (vous pouvez appeler la méthode next et l'utiliser dans une boucle for). Ce quelque chose a un nom en Python appelé Générateur

l'Espérance qui clarifie un peu.

6
répondu Heapify 2017-09-09 01:31:09

Générateur De Fonction, Objet Du Générateur, Groupe Électrogène:

A Generator function est exactement comme une fonction régulière en Python mais elle contient une ou plusieurs instructions yield . Fonctions de générateur est un excellent outil pour créer Iterator objets aussi facile que possible. Le Iterator retour d'objet par la fonction de générateur est également appelé Générateur d'objet ou Générateur .

dans cet exemple, j'ai créé une fonction génératrice qui renvoie un objet générateur <generator object fib at 0x01342480> . Tout comme les autres itérateurs, les objets de générateur 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

ainsi une fonction de générateur est la manière la plus facile pour créer un objet itérateur.

Itérateur :

Tous "les 1519100920" générateur d'objet est un itérateur , mais pas vice-versa. Un objet itérateur personnalisé peut être créé si sa classe implémente la méthode __iter__ et __next__ (aussi appelé protocole itérateur).

cependant, il est beaucoup plus facile d'utiliser des générateurs fonction pour créer itérateurs parce qu'ils simplifient leur création, mais un itérateur personnalisé vous donne plus de liberté et vous pouvez également mettre en œuvre d'autres méthodes en fonction de vos exigences comme indiqué dans l'exemple ci-dessous.

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
4
répondu N Randhawa 2016-11-12 18:11:37

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 prend beaucoup moins de mémoire car il n'a pas besoin de stocker toutes les valeurs en mémoire en même temps.

4
répondu tashuhka 2017-03-16 15:45:57

les réponses précédentes ont manqué cet ajout: un générateur a une méthode close , alors que les itérateurs typiques ne le font pas. La méthode close déclenche une exception StopIteration dans le générateur, qui peut être prise dans une clause finally dans cet itérateur, pour obtenir une chance d'exécuter un peu de nettoyage. Cette abstraction la rend plus utilisable dans les grands itérateurs que dans les itérateurs simples. On peut fermer un générateur comme on peut fermer un fichier, sans avoir à se soucier de ce qu'il y a en dessous.

cela dit, ma réponse personnelle à la première question serait: iteratable a une méthode __iter__ seulement, les itérateurs typiques ont une méthode __next__ seulement, 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 à favoriser les générateurs beaucoup, car il est plus résilient: la méthode close une plus grande composabilité avec yield from . Localement, je peux utiliser des itérateurs, mais seulement si c'est une structure plate et simple (itérateurs ne compose pas facilement) et s'il y a des raisons de croire que la séquence est assez courte, surtout si elle peut être arrêté avant qu'elle atteigne la fin. J'ai tendance à considérer les itérateurs comme des primitifs de bas niveau, sauf comme des littéraux.

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

4
répondu Hibou57 2018-01-03 23:20:15