Comment créer une fonction itérative (ou objet itérateur) en python?
Les objets itérateur en python sont conformes au protocole itérateur, ce qui signifie essentiellement qu'ils fournissent deux méthodes: __iter__()
et __next__()
.
Le __iter__
renvoie l'objet itérateur et est implicitement appelé au début des boucles.
La __next__()
méthode renvoie la valeur suivante et est implicitement appelée à chaque incrément de boucle. Cette méthode déclenche une exception StopIteration lorsqu'il n'y a plus de valeur à renvoyer, qui est implicitement capturée par les constructions en boucle pour arrêter l'itération.
Voici un exemple simple de compteur:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Cela imprimera:
3
4
5
6
7
8
Il est plus facile d'écrire à l'aide d'un générateur, comme indiqué dans une réponse précédente:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
La sortie imprimée sera la même. Sous le capot, l'objet générateur prend en charge le protocole itérateur et fait quelque chose à peu près similaire à la classe Counter.
L'article de David Mertz, Iterators and Simple Generators , est une très bonne introduction.
__next__
.counter
est un itérateur, mais ce n'est pas une séquence. Il ne stocke pas ses valeurs. Par exemple, vous ne devez pas utiliser le compteur dans une boucle for doublement imbriquée.__iter__
(en plus de in__init__
). Sinon, l'objet ne peut être itéré qu'une seule fois. Par exemple, si vous ditesctr = Counters(3, 8)
, vous ne pouvez pas utiliserfor c in ctr
plus d'une fois.Counter
est un itérateur, et les itérateurs ne sont censés être itérés qu'une seule fois. Si vous rétablissezself.current
dans__iter__
, puis une boucle sur la imbriquéeCounter
serait complètement brisé, et toutes sortes de comportements présumés de itérateurs (que l' appeliter
sur eux est idempotent) ne sont pas respectés. Si vous voulez pouvoir itérerctr
plusieurs fois, il doit s'agir d'un itérateur non itératif, où il renvoie un nouvel itérateur à chaque fois qu'il__iter__
est invoqué. Essayer de mélanger et de faire correspondre (un itérateur qui est implicitement réinitialisé lorsqu'il__iter__
est invoqué) viole les protocoles.Counter
devait être un non-itérateur itérable, vous supprimeriez la définition de__next__
/next
entièrement, et redéfiniriez probablement__iter__
comme une fonction de générateur de la même forme que le générateur décrit à la fin de cette réponse (sauf au lieu des limites venant d'arguments__iter__
, ils seraient arguments__init__
enregistrés surself
et accessible à partirself
de__iter__
).Il existe quatre façons de créer une fonction itérative:
__iter__
et__next__
(ounext
en Python 2.x))__getitem__
)Exemples:
Pour voir les quatre méthodes en action:
Ce qui se traduit par:
Remarque :
Les deux types de générateurs (
uc_gen
etuc_genexp
) ne peuvent pas l'êtrereversed()
; le simple itérateur (uc_iter
) aurait besoin de la__reversed__
méthode magique (qui, selon les documents , doit renvoyer un nouvel itérateur, mais renvoyant desself
travaux (au moins en CPython)); et le getitem iteratable (uc_getitem
) doit avoir la__len__
méthode magique:Pour répondre à la question secondaire du colonel Panic sur un itérateur infiniment évalué paresseusement, voici ces exemples, en utilisant chacune des quatre méthodes ci-dessus:
Ce qui se traduit par (au moins pour ma série d'échantillons):
Comment choisir lequel utiliser? C'est surtout une question de goût. Les deux méthodes que je vois le plus souvent sont les générateurs et le protocole itérateur, ainsi qu'un hybride (
__iter__
renvoyant un générateur).Les expressions de générateur sont utiles pour remplacer les compréhensions de liste (elles sont paresseuses et peuvent donc économiser des ressources).
Si l'on a besoin de compatibilité avec les versions antérieures de Python 2.x, utilisez
__getitem__
.la source
uc_iter
devrait expirer lorsqu'elle est terminée (sinon elle le serait par infini); si vous voulez recommencer, vous devez obtenir un nouvel itérateur en appelant àuc_iter()
nouveau.self.index = 0
en__iter__
sorte que vous pouvez itérer plusieurs fois. Sinon, vous ne pouvez pas.Tout d'abord, le module itertools est incroyablement utile pour toutes sortes de cas dans lesquels un itérateur serait utile, mais voici tout ce dont vous avez besoin pour créer un itérateur en python:
N'est-ce pas cool? Le rendement peut être utilisé pour remplacer un retour normal dans une fonction. Il renvoie l'objet de la même manière, mais au lieu de détruire l'état et de quitter, il enregistre l'état lorsque vous souhaitez exécuter l'itération suivante. En voici un exemple en action tiré directement de la liste des fonctions itertools :
Comme indiqué dans la description des fonctions (c'est la fonction count () du module itertools ...), il produit un itérateur qui retourne des entiers consécutifs commençant par n.
Les expressions de générateur sont un tout autre bidon de vers (vers impressionnants!). Ils peuvent être utilisés à la place d'une compréhension de liste pour économiser de la mémoire (les compréhensions de liste créent une liste en mémoire qui est détruite après utilisation si elle n'est pas affectée à une variable, mais les expressions de générateur peuvent créer un objet générateur ... qui est un moyen sophistiqué de disant Iterator). Voici un exemple de définition d'expression de générateur:
Ceci est très similaire à notre définition d'itérateur ci-dessus, sauf que la plage complète est prédéterminée entre 0 et 10.
Je viens de trouver xrange () (surpris de ne pas l'avoir vu auparavant ...) et je l'ai ajouté à l'exemple ci-dessus. xrange () est une version itérable de range () qui a l'avantage de ne pas précompiler la liste. Ce serait très utile si vous aviez un corpus de données géant à parcourir et que vous n'aviez que trop de mémoire pour le faire.
la source
Je vois certains d' entre vous faire
return self
dans__iter__
. Je voulais juste noter que__iter__
lui - même peut être un générateur (supprimant ainsi le besoin__next__
et soulevant desStopIteration
exceptions)Bien sûr, ici, on pourrait aussi bien faire directement un générateur, mais pour les classes plus complexes, cela peut être utile.
la source
return self
en dedans__iter__
. Quand j'allais essayer de l'utiliseryield
, j'ai trouvé que votre code faisait exactement ce que je voulais essayer.next()
?return iter(self).next()
?self.current
ou tout autre compteur. Cela devrait être la meilleure réponse!iter
instances de la classe, mais ce ne sont pas eux-mêmes des instances de la classe.Cette question concerne les objets itérables, pas les itérateurs. En Python, les séquences sont également itérables donc une façon de faire une classe itérable est de la faire se comporter comme une séquence, c'est-à-dire de lui donner
__getitem__
et des__len__
méthodes. J'ai testé cela sur Python 2 et 3.la source
__len__()
méthode.__getitem__
seul avec le comportement attendu suffit.Toutes les réponses sur cette page sont vraiment excellentes pour un objet complexe. Mais pour ceux contenant builtin types itérables comme attributs, comme
str
,list
,set
oudict
, ou toute mise en œuvre decollections.Iterable
, vous pouvez omettre certaines choses dans votre classe.Il peut être utilisé comme:
la source
return iter(self.string)
.Il s'agit d'une fonction itérable sans
yield
. Il utilise laiter
fonction et une fermeture qui maintient son état dans un mutable (list
) dans la portée englobante de python 2.Pour Python 3, l'état de fermeture est conservé dans une immuable dans la portée englobante et
nonlocal
est utilisé dans la portée locale pour mettre à jour la variable d'état.Tester;
la source
iter
, mais juste pour être clair: c'est plus complexe et moins efficace que d'utiliser simplement uneyield
fonction de générateur basé; Python a une tonne de support d'interpréteur pour lesyield
fonctions de générateur basées dont vous ne pouvez pas profiter ici, ce qui rend ce code considérablement plus lent. Voté néanmoins.Si vous cherchez quelque chose de court et de simple, cela vous suffira peut-être:
exemple d'utilisation:
la source
Inspiré par la réponse de Matt Gregory, voici un itérateur un peu plus compliqué qui renverra a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
la source