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.
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.
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érateursPython 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__()
etnext()
[__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é?".
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
>>>
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érateurssont 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é.
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.
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
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.
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.