Question

Comment créer une fonction itérative (ou un objet itérateur) en python ?

Était-ce utile?

La solution

Les objets itérateurs 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.Le next() La méthode renvoie la valeur suivante et est implicitement appelée à chaque incrément de boucle. next() lève 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
        self.high = high

    def __iter__(self):
        return self

    def next(self): # Python 3: def __next__(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1


for c in Counter(3, 8):
    print c

Cela imprimera :

3
4
5
6
7
8

C'est plus facile à é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, 8):
    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, Itérateurs et générateurs simples, est une très bonne introduction.

Autres conseils

Il existe quatre façons de créer une fonction itérative :

Exemples:

# generator
def uc_gen(text):
    for char in text:
        yield char.upper()

# generator expression
def uc_genexp(text):
    return (char.upper() for char in text)

# iterator protocol
class uc_iter():
    def __init__(self, text):
        self.text = text
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.text[self.index].upper()
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

# getitem method
class uc_getitem():
    def __init__(self, text):
        self.text = text
    def __getitem__(self, index):
        result = self.text[index].upper()
        return result

Pour voir les quatre méthodes en action :

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
    for ch in iterator('abcde'):
        print ch,
    print

Ce qui se traduit par :

A B C D E
A B C D E
A B C D E
A B C D E

Note:

Les deux types de générateurs (uc_gen et uc_genexp) c'est pas possible reversed();l'itérateur simple (uc_iter) aurait besoin du __reversed__ méthode magique (qui doit renvoyer un nouvel itérateur qui va en arrière) ;et le getitem itérable (uc_getitem) doit avoir le __len__ méthode magique :

    # for uc_iter
    def __reversed__(self):
        return reversed(self.text)

    # for uc_getitem
    def __len__(self)
        return len(self.text)

Pour répondre à la question secondaire du colonel Panic sur un itérateur infini évalué paresseusement, voici ces exemples, en utilisant chacune des quatre méthodes ci-dessus :

# generator
def even_gen():
    result = 0
    while True:
        yield result
        result += 2


# generator expression
def even_genexp():
    return (num for num in even_gen())  # or even_iter or even_getitem
                                        # not much value under these circumstances

# iterator protocol
class even_iter():
    def __init__(self):
        self.value = 0
    def __iter__(self):
        return self
    def __next__(self):
        next_value = self.value
        self.value += 2
        return next_value

# getitem method
class even_getitem():
    def __getitem__(self, index):
        return index * 2

import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
    limit = random.randint(15, 30)
    count = 0
    for even in iterator():
        print even,
        count += 1
        if count >= limit:
            break
    print

Ce qui donne (au moins pour mon exemple d'exécution) :

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32

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 :

rendement

N'est-ce pas cool ?Le rendement peut être utilisé pour remplacer un rendement normal retour 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 du liste des fonctions itertools:

def count(n=0):
    while True:
        yield n
        n += 1

Comme indiqué dans la description des fonctions (c'est le compter() fonction du module itertools...), il produit un itérateur qui renvoie des entiers consécutifs commençant par n.

Expressions génératrices sont une toute autre boîte de Pandore (des vers géniaux !).Ils peuvent être utilisés à la place d'un Compréhension de la 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 génératrices peuvent créer un objet générateur...ce qui est une façon élégante de dire Iterator).Voici un exemple de définition d'expression génératrice :

gen = (n for n in xrange(0,11))

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 plagex() (surpris de ne pas l'avoir vu auparavant...) et je l'ai ajouté à l'exemple ci-dessus. plagex() est une version itérable de gamme() ce qui a l'avantage de ne pas préconstruire la liste.Ce serait très utile si vous disposiez d'un corpus géant de données sur lequel parcourir et d'une quantité limitée de mémoire pour le faire.

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 (éliminant ainsi le besoin de __next__ et élever StopIteration des exceptions)

class range:
  def __init__(self,a,b):
    self.a = a
    self.b = b
  def __iter__(self):
    i = self.a
    while i < self.b:
      yield i
      i+=1

Bien sûr ici autant faire directement un générateur, mais pour des classes plus complexes cela peut être utile.

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 créer une classe itérable est de la faire se comporter comme une séquence, c'est-à-diredonne-le __getitem__ et __len__ méthodes.J'ai testé cela sur Python 2 et 3.

class CustomRange:

    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __getitem__(self, item):
        if item >= len(self):
            raise IndexError("CustomRange index out of range")
        return self.low + item

    def __len__(self):
        return self.high - self.low


cr = CustomRange(0, 10)
for i in cr:
    print(i)

Il s'agit d'une fonction itérable sans yield.Il utilise le iter fonction et une fermeture qui maintient son état dans un état mutable (list) dans la portée englobante de python 2.

def count(low, high):
    counter = [0]
    def tmp():
        val = low + counter[0]
        if val < high:
            counter[0] += 1
            return val
        return None
    return iter(tmp, None)

Pour Python 3, l'état de fermeture est conservé dans un état immuable dans la portée englobante et nonlocal est utilisé dans la portée locale pour mettre à jour la variable d'état.

def count(low, high):
    counter = 0
    def tmp():
        nonlocal counter
        val = low + counter
        if val < high:
            counter += 1
            return val
        return None
    return iter(tmp, None)  

Test;

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9

Toutes les réponses sur cette page sont vraiment idéales pour un objet complexe.Mais pour ceux qui contiennent des types itérables intégrés comme attributs, comme str, list, set ou dict, ou toute mise en œuvre de collections.Iterable, vous pouvez omettre certaines choses dans votre classe.

class Test(object):
    def __init__(self, string):
        self.string = string

    def __iter__(self):
        # since your string is already iterable
        return (ch for ch in string)

Il peut être utilisé comme :

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e

Si vous cherchez quelque chose de court et simple, cela vous suffira peut-être :

class A(object):
    def __init__(self, l):
        self.data = l

    def __iter__(self):
        return iter(self.data)

exemple d'utilisation :

In [3]: a = A([2,3,4])

In [4]: [i for i in a]
Out[4]: [2, 3, 4]

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

    class AlphaCounter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 3: def __next__(self)
        alpha = ' abcdefghijklmnopqrstuvwxyz'
        n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
        n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
        if n_current > n_high:
            raise StopIteration
        else:
            increment = True
            ret = ''
            for x in self.current[::-1]:
                if 'z' == x:
                    if increment:
                        ret += 'a'
                    else:
                        ret += 'z'
                else:
                    if increment:
                        ret += alpha[alpha.find(x)+1]
                        increment = False
                    else:
                        ret += x
            if increment:
                ret += 'a'
            tmp = self.current
            self.current = ret[::-1]
            return tmp

for c in AlphaCounter('a', 'zzz'):
    print(c)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top