Construir um Python Iterator Básico

votos
442

Como se poderia criar uma função iterativa (ou objeto iterador) em python?

Publicado 21/08/2008 em 01:36
fonte usuário
Em outras línguas...                            


9 respostas

votos
517

Iteradoras objectos em python estar em conformidade com o protocolo de iteração, o qual significa basicamente que proporcionam dois métodos: __iter__() e next(). As __iter__retorna o objeto iterador e é chamado implicitamente no início de loops. O next()método retorna o próximo valor e é chamado implicitamente a cada incremento loop. next()gera uma exceção StopIteration quando não há mais valor para voltar, que é implicitamente capturado por construções de laço para parar a iteração.

Aqui está um exemplo simples de um contador:

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

Isto irá imprimir:

3
4
5
6
7
8

Este é mais fácil escrever usando um gerador, como coberto em uma resposta anterior:

def counter(low, high):
    current = low
    while current <= high:
        yield current
        current += 1

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

O resultado da impressão será o mesmo. Sob o capô, o objeto gerador suporta o protocolo iterador e faz algo mais ou menos parecido com o Contador de classe.

O artigo de David Mertz, iteradores e simples Geradores , é uma boa introdução.

Respondeu 23/08/2008 em 17:57
fonte usuário

votos
324

Há quatro maneiras de construir uma função iterativa:

Exemplos:

# 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

Para ver todos os quatro métodos em ação:

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

O que resulta em:

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

Nota :

Os dois tipos de geradores ( uc_gene uc_genexp) não pode ser reversed(); o iterador simples ( uc_iter) seria necessário o __reversed__método mágico (que deve retornar um novo iterador que vai para trás); eo getitem iteratable ( uc_getitem) deve ter o __len__método mágico:

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

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

Para responder à pergunta secundário do Cel pânico sobre uma iteração preguiçosamente avaliada infinito, aqui são os exemplos, utilizando cada um dos quatro métodos acima:

# 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

O que resulta em (pelo menos para minha corrida de amostra):

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
Respondeu 24/09/2011 em 23:13
fonte usuário

votos
97

Primeiro de tudo o módulo itertools é incrivelmente útil para todos os tipos de casos em que um iterador seria útil, mas aqui é tudo que você precisa para criar um iterador em python:

produção

Não é legal? Rendimento pode ser usado para substituir um normal de retorno em uma função. Ele retorna o objeto a mesma coisa, mas em vez de destruí estado e sair, ele salva estado para quando você quer executar a próxima iteração. Aqui está um exemplo dele em ação puxado directamente a partir do lista de funções itertools :

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

Como indicado na descrição funções (é o count () função do módulo itertools ...), que produz um iterador que retorna inteiros consecutivos começando com n.

Gerador de expressões são toda uma outra lata de vermes (vermes impressionantes!). Eles podem ser usados no lugar de uma compreensão de lista para economizar memória (compreensões lista criar uma lista na memória que é destruída após o uso se não for atribuído a uma variável, mas gerador de expressões pode criar um objeto Generator ... o que é uma maneira extravagante de dizendo Iterator). Aqui está um exemplo de uma definição de expressão gerador:

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

Isto é muito semelhante à nossa definição iteração acima, excepto a gama é pré-determinado para ser entre 0 e 10.

Eu só descobri xrange () (surpreendidos eu não tinha visto isso antes ...) e acrescentou que para o exemplo acima. xrange () é uma versão iterable de range () , que tem a vantagem de não prebuilding lista. Seria muito útil se você tivesse um corpus gigantesco de dados para iterar e só tinha tanta memória para fazê-lo em.

Respondeu 21/08/2008 em 01:36
fonte usuário

votos
81

Vejo alguns de vocês fazendo return selfem __iter__. Eu só queria observar que __iter__em si pode ser um gerador (eliminando assim a necessidade de __next__e levantando StopIterationexceções)

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

Claro que aqui se poderia muito bem fazer directamente um gerador, mas para as classes mais complexas, pode ser útil.

Respondeu 27/07/2012 em 16:05
fonte usuário

votos
7

Esta pergunta é sobre objetos iteráveis, não sobre iteradores. Em Python, sequências são iteráveis também portanto uma maneira de fazer uma classe iteráveis é torná-lo comportar-se como uma sequência, ou seja, dar-lhe __getitem__e __len__métodos. Eu testei isso em Python 2 e 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)
Respondeu 21/03/2016 em 17:39
fonte usuário

votos
3

Esta é uma função iterable sem yield. É fazer uso da iterfunção e um fecho que mantém o estado em um mutável ( list) no âmbito anexando para 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)

Para Python 3, estado de fecho é mantido numa imutável no âmbito envolvente e nonlocalé utilizado em bito local para actualizar a variável de estado.

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)  

Teste;

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9
Respondeu 03/03/2016 em 17:55
fonte usuário

votos
1

Se você está procurando algo curto e simples, talvez seja o suficiente para você:

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

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

Exemplo de utilização:

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

In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Respondeu 26/04/2018 em 08:38
fonte usuário

votos
0

Todas as respostas nesta página são realmente grandes para um objeto complexo. Mas para aqueles que contêm tipos iteradoras embutidas como atributos, como str, list, setou dict, ou em qualquer implementação de collections.Iterable, você pode omitir certas coisas em sua 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)

Ele pode ser usado como:

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

# prints
# a
# b
# c
# d
# e
Respondeu 14/08/2018 em 08:25
fonte usuário

votos
0

Inspirado pela resposta de Matt Gregory aqui é um pouco mais complicado iterador que irá retornar 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)
Respondeu 13/07/2018 em 17:34
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more