Question Construire un itérateur Python de base


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


452
2017-08-21 00:36


origine


Réponses:


Les objets Iterator en python sont conformes au protocole iterator, 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() 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
        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 va imprimer:

3
4
5
6
7
8

Ceci est plus facile à écrire en utilisant un générateur, comme couvert 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 de l'itérateur et fait quelque chose de grossièrement similaire à la classe Counter.

L'article de David Mertz, Iterators et générateurs simples, est une très bonne introduction.


533
2017-08-23 16:57



Il existe quatre manières 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 entraîne:

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

Remarque:

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 de __reversed__ méthode magique (qui doit renvoyer un nouvel itérateur qui recule); 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 à propos d'un itérateur infiniment paresseux, voici quelques 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 entraîne (au moins pour mon exemple):

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

331
2017-09-24 22:13



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 produit normal revenir dans une fonction. Il retourne l'objet tout de même, mais au lieu de détruire l'état et de quitter, il sauve l'état lorsque vous voulez exécuter l'itération suivante. En voici un exemple en action tiré directement du Liste des fonctions d'itertools:

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

Comme indiqué dans la description des fonctions (c'est la compter() fonction 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 une toute autre canette de vers (des vers impressionnants!). Ils peuvent être utilisés à la place d'un Compréhension de la liste pour économiser de la mémoire (la liste des compréhensions crée une liste dans la 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 Generator ... ce qui est une façon de dire Iterator). Voici un exemple de définition d'une expression de générateur:

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 pour être comprise entre 0 et 10.

Je viens de trouver xrange () (surpris je ne l'avais pas vu avant ...) et l'a ajouté à l'exemple ci-dessus. xrange () 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 aviez un corpus de données géant à parcourir et que vous n'aviez que trop de mémoire pour le faire.


98
2017-08-21 00:36



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 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, on pourrait aussi bien faire directement un générateur, mais pour des classes plus complexes, cela peut être utile.


84
2017-07-27 15:05



Cette question concerne les objets itérables, pas les itérateurs. En Python, les séquences sont itérables aussi 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 __len__ méthodes J'ai testé ceci 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)

8
2018-03-21 17:39



C'est une fonction itérable sans yield. Il utilise le iter fonction et une fermeture qui le maintient dans un 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 une 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)  

Tester;

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

3
2018-03-03 17:55



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]

1
2018-04-26 08:38



Inspiré par la réponse de Matt Gregory voici un itérateur un peu plus compliqué qui retournera 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)

0
2017-07-13 17:34