Construir uma Python Básico Iterador
Pergunta
Como é que iria criar um processo iterativo de função (ou iterator objeto) em python?
Solução
O iterador de objetos em python conformidade com o iterador protocol), que basicamente significa que eles fornecem dois métodos: __iter__()
e next()
.O __iter__
retorna o iterador objeto e é chamado implicitamente no início de loops.O next()
método retorna o próximo valor e é chamado implicitamente em cada ciclo de incremento. next()
levanta um StopIteration exceção quando não há mais valor para voltar, o que é implicitamente capturado por construções de loop 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
Isto é mais fácil de escrever, usando um gerador, conforme abordado 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
A saída impressa vai ser o mesmo.Sob o capô, o objeto de gerador suporta o iterador de protocolo e faz algo mais ou menos semelhante para a classe Contador.
David Mertz do artigo, Os iteradores e Simples Geradores de, é uma boa introdução.
Outras dicas
Existem quatro formas de se construir um processo iterativo de função:
- criar um gerador (usa o rendimento palavra-chave)
- usar um gerador de expressão (genexp)
- criar um iterador (define
__iter__
e__next__
(ounext
no Python 2.x)) - criar uma classe que Python pode iterar sobre seu próprio (define
__getitem__
)
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
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_gen
e uc_genexp
) não pode ser reversed()
;o simples iterador (uc_iter
) seria necessário que a __reversed__
método mágico (que deve retornar um novo iterador que vai para trás);e o 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)
A resposta do Coronel de Pânico secundário pergunta sobre um infinito preguiçosamente avaliado iterador, aqui estão os exemplos, usando 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 o meu exemplo de execução):
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
O primeiro de todos os módulo itertools é incrivelmente útil para todos os tipos de casos em que um iterador que poderia ser útil, mas aqui está tudo que você precisa para criar um iterador em python:
rendimento
Não é legal?Rendimento pode ser usado para substituir um normal retorno em uma função.Ele retorna o objeto da mesma, mas em vez de destruir o estado e de sair, ele guarda do estado, para quando você deseja executar a próxima iteração.Aqui está um exemplo de que em ação obtida diretamente a partir da itertools lista de funções:
def count(n=0):
while True:
yield n
n += 1
Como dito na descrição de funcionalidades (é o count() a função do módulo itertools...) , produz um iterador que retorna inteiros consecutivos, começando com n.
Gerador de expressões são uma outra lata de vermes (impressionante vermes!).Eles podem ser usados no lugar de um Lista De Compreensão para economizar memória (lista de compreensões criar uma lista na memória que é destruído após o uso, se não for atribuído a uma variável, mas o gerador de expressões pode criar um Objeto de Gerador de...que é uma maneira elegante de dizer o Iterator).Aqui está um exemplo de um gerador de definição de expressão:
gen = (n for n in xrange(0,11))
Este é muito semelhante ao nosso iterador definição acima, exceto a linha completa é predeterminado a ser entre 0 e 10.
Eu só encontrei xrange() (surpreso que eu não tinha visto isso antes...) e adicionado para o exemplo acima. xrange() é uma versão do iterable faixa (a) o que tem a vantagem de não pré-criando a lista.Seria muito útil se você tivesse um gigante corpus de dados para iterar sobre e só tinha tanta memória para fazê-lo em.
Eu vejo que alguns de vocês fazendo return self
no __iter__
.Eu só queria observar que __iter__
em si pode ser um gerador (eliminando assim a necessidade de __next__
e levantando StopIteration
excepçõ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 pode-se também fazer diretamente um gerador, mas para classes mais complexa que possa ser útil.
Esta pergunta é sobre iterable objetos, e não sobre os iteradores.Em Python, as seqüências são iterable também, então uma maneira de fazer uma iterable classe faze-lo se comportar como uma seqüência, isto é,dê a ele __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)
Este é um iterable função, sem yield
.Fazer uso do iter
função e um fecho que mantém o estado mutável (list
) no âmbito envolvente 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, fechamento de estado é mantido em uma imutável no âmbito envolvente e nonlocal
é utilizado em âmbito local para atualizar 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)
O teste;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
Todas as respostas nesta página são realmente grandes para um objeto complexo.Mas para aqueles que contenham builtin iterable tipos de atributos, como str
, list
, set
ou dict
, ou 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
Se você procura algo simples e curto, talvez ele vai ser o suficiente para você:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
exemplo de uso:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Inspirado por Matt Gregory resposta aqui é um pouco mais complicado do iterador que 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)