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.
Réponses
Trop de publicités?Exemples de Ned Batchelder fortement recommandé pour les itérateurs et les générateurs
Une méthode sans générateurs qui font quelque chose aux nombres pairs
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
alors qu'en utilisant un générateur
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
- Nous n'avons pas besoin liste ni un
return
déclaration - Efficace pour les flux de longueurs importantes/ infinies ... il se contente de marcher et de donner la valeur.
Appeler le evens
La méthode (générateur) est comme d'habitude
num = [...]
for n in evens(num):
do_smth(n)
- Générateur également utilisé pour casser la double boucle
Itérateur
Un livre plein de pages est un itérable Un signet est un itérateur
et ce signet n'a rien à faire sauf à bouger 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 l'Iterator ... nous avons besoin de next
y iter
Comme cela a été dit :
Une fonction Generator renvoie un objet itérateur.
Tout l'intérêt de l'Iterator :
Stocker un élément à la fois en mémoire
Les réponses précédentes ont omis cet ajout : un générateur a un close
alors que les itérateurs classiques ne le font pas. Le site close
déclenche un StopIteration
dans le générateur, qui peut être attrapé dans une finally
dans cet itérateur, pour avoir une chance d'effectuer un nettoyage. Cette abstraction le rend plus utilisable dans les itérateurs plus grands que simples. 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érable a un caractère __iter__
seulement, les itérateurs typiques ont une méthode __next__
uniquement, les générateurs ont à la fois une méthode __iter__
et un __next__
et un autre close
.
Pour la deuxième question, ma réponse personnelle serait la suivante : dans une interface publique, j'ai tendance à privilégier les générateurs, car ils sont plus résilients : l'option close
et 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 se composent pas facilement) et s'il y a des raisons de croire que la séquence est plutôt courte, surtout 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, 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 : les deux sont abstraits et composables.
Il est difficile de répondre à la question sans deux autres concepts : iterable
y iterator protocol
.
-
Quelle est la différence entre
iterator
yiterable
? Conceptuellement, vous itérez suriterable
à l'aide de l'outil correspondantiterator
. Il y a quelques différences qui peuvent aider à distingueriterator
yiterable
dans la pratique :- Une différence est que
iterator
a__next__
méthode,iterable
ne le fait pas. - Une autre différence - les deux contiennent
__iter__
méthode. En cas deiterable
il renvoie l'itérateur correspondant. En cas deiterator
il se retourne lui-même. Cela peut aider à distingueriterator
yiterable
dans la pratique.
x = [1, 2, 3] dir(x) [... iter ...] x_iter = iter(x) dir(x_iter) [... iter ... next ...] type(x_iter) list_iterator
- Une différence est que
-
Quels sont les
iterables
enpython
?list
,string
,range
etc. Quels sont lesiterators
?enumerate
,zip
,reversed
etc. Nous pouvons vérifier cela en utilisant l'approche ci-dessus. C'est un peu confus. Il serait probablement plus simple de n'avoir qu'un seul type. Y a-t-il une différence entrerange
yzip
? L'une des raisons de faire cela -range
a beaucoup de fonctionnalités supplémentaires - nous pouvons l'indexer ou vérifier s'il contient un certain nombre de chiffres, etc. aquí ). -
Comment pouvons-nous créer un
iterator
nous-mêmes ? En théorie, nous pouvons mettre en œuvreIterator Protocol
(voir aquí ). Nous devons écrire__next__
y__iter__
et souleverStopIteration
et ainsi de suite (voir la réponse d'Alex Martelli pour un exemple et une motivation possible, voir aussi aquí ). Mais en pratique, nous utilisons des générateurs. Il semble que ce soit de loin la principale méthode pour créer desiterators
enpython
.
Je peux vous donner quelques autres exemples intéressants qui montrent une utilisation quelque peu déroutante de ces concepts dans la pratique :
- sur
keras
nous avonstf.keras.preprocessing.image.ImageDataGenerator
; cette classe n'a pas__next__
y__iter__
Il ne s'agit donc pas d'un itérateur (ou d'un générateur) ; - si vous appelez son
flow_from_dataframe()
vous obtiendrezDataFrameIterator
qui a ces méthodes, mais il n'implémente pasStopIteration
(ce qui n'est pas courant dans les itérateurs intégrés à l'applicationpython
) ; dans la documentation, nous pouvons lire que "ADataFrameIterator
donnant des tuples de(x, y)
"Une fois de plus, la terminologie utilisée prête à confusion ; - nous avons également
Sequence
classe danskeras
et c'est une implémentation personnalisée d'une fonctionnalité de générateur (les générateurs réguliers ne sont pas adaptés au multithreading) mais elle n'implémente pas__next__
y__iter__
il s'agit plutôt d'une enveloppe autour des générateurs (elle utilise lesyield
) ;
Fonction de générateur, Objet de générateur, Générateur :
A Fonction de générateur est comme une fonction ordinaire en Python, mais elle contient un ou plusieurs éléments suivants yield
déclarations. Le générateur de fonctions est un excellent outil pour créer Itérateur des objets aussi facilement que possible. Le site Itérateur L'objet renvoyé par la fonction de générateur est également appelé Objet générateur o Générateur .
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 un processus de 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
Une 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 met en œuvre les éléments suivants __iter__
y __next__
(également appelé protocole d'itération).
Cependant, il est beaucoup plus facile d'utiliser la fonction des générateurs pour créer itérateurs parce qu'ils simplifient leur création, mais un Iterator personnalisé vous donne plus de liberté et vous pouvez également implémenter d'autres méthodes selon vos besoins comme le montre 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