Como alguém criaria uma função iterativa (ou objeto iterador) em python?
Objetos iteradores em python estão em conformidade com o protocolo iterador, o que basicamente significa que eles fornecem dois métodos: __iter__()
e __next__()
.
O __iter__
retorna o objeto iterador e é chamado implicitamente no início dos loops.
O __next__()
método retorna o próximo valor e é chamado implicitamente a cada incremento de loop. Esse método gera uma exceção StopIteration quando não há mais valor a ser retornado, que é capturado implicitamente por construções em loop para interromper a iteração.
Aqui está um exemplo simples de um contador:
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)
Isso imprimirá:
3
4
5
6
7
8
É mais fácil 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, 9):
print(c)
A saída impressa será a mesma. Sob o capô, o objeto gerador suporta o protocolo iterador e faz algo aproximadamente semelhante à classe Counter.
O artigo de David Mertz, Iteradores e Geradores Simples , é uma introdução muito boa.
__next__
.counter
é um iterador, mas não é uma sequência. Não armazena seus valores. Você não deve usar o contador em um loop for duplamente aninhado, por exemplo.__iter__
(além de em__init__
). Caso contrário, o objeto poderá ser iterado apenas uma vez. Por exemplo, se você dizctr = Counters(3, 8)
, não pode usarfor c in ctr
mais de uma vez.Counter
é um iterador, e iteradores devem ser iterados apenas uma vez. Se você redefinirself.current
em__iter__
, em seguida, um loop aninhado sobre oCounter
seria completamente quebrado, e todos os tipos de comportamentos assumidos de iterators (que chamariter
sobre eles é idempotent) são violados. Se você deseja iterarctr
mais de uma vez, ele precisa ser iterável não iterador, onde retorna um iterador novinho em folha cada vez que__iter__
é invocado. Tentar misturar e combinar (um iterador que é implicitamente redefinido quando__iter__
invocado) viola os protocolos.Counter
fosse iterável para não iterador, você removeria a definição de__next__
/next
inteiramente e provavelmente redefiniria__iter__
como uma função de gerador da mesma forma que o gerador descrito no final desta resposta (exceto em vez dos limites) vindo de argumentos para__iter__
, eles seriam argumentos para serem__init__
salvosself
e acessados deself
dentro__iter__
).Existem quatro maneiras de criar uma função iterativa:
__iter__
e__next__
(ounext
no Python 2.x))__getitem__
)Exemplos:
Para ver todos os quatro métodos em ação:
O que resulta em:
Nota :
Os dois tipos de gerador (
uc_gen
euc_genexp
) não podem serreversed()
; o iterador simples (uc_iter
) precisaria do__reversed__
método mágico (que, de acordo com os documentos , deve retornar um novo iterador, mas o retornoself
funciona (pelo menos no CPython)); e o getitem iteratable (uc_getitem
) deve ter o__len__
método mágico:Para responder à pergunta secundária do coronel Panic sobre um iterador infinito avaliado preguiçosamente, aqui estão esses exemplos, usando cada um dos quatro métodos acima:
O que resulta em (pelo menos para a minha amostra):
Como escolher qual usar? Isso é principalmente uma questão de gosto. Os dois métodos que vejo com mais freqüência são geradores e o protocolo iterador, além de um híbrido (
__iter__
retornando um gerador).As expressões do gerador são úteis para substituir as compreensões da lista (são preguiçosas e podem economizar recursos).
Se for necessário compatibilidade com versões anteriores do Python 2.x, use
__getitem__
.fonte
uc_iter
deve expirar quando terminar (caso contrário, seria infinita); se você quiser fazer isso novamente, precisará obter um novo iterador ligandouc_iter()
novamente.self.index = 0
em__iter__
para que você possa interagir muitas vezes. Caso contrário, você não pode.Primeiro de tudo, o módulo itertools é incrivelmente útil para todos os tipos de casos em que um iterador seria útil, mas aqui é tudo o que você precisa para criar um iterador em python:
Isso não é legal? O rendimento pode ser usado para substituir um retorno normal em uma função. Ele retorna o objeto da mesma forma, mas em vez de destruir o estado e sair, ele salva o estado para quando você deseja executar a próxima iteração. Aqui está um exemplo disso em ação, extraído diretamente da lista de funções do itertools :
Conforme indicado na descrição das funções (é a função count () do módulo itertools ...), produz um iterador que retorna números inteiros consecutivos começando com n.
As expressões de gerador são uma outra lata de worms (worms impressionantes!). Eles podem ser usados no lugar de uma Compreensão de lista para economizar memória (as compreensões de lista criam uma lista na memória que é destruída após o uso, se não for atribuída a uma variável, mas as expressões geradoras podem criar um Objeto Gerador ... que é uma maneira elegante de dizendo Iterador). Aqui está um exemplo de uma definição de expressão do gerador:
Isso é muito semelhante à nossa definição de iterador acima, exceto que o intervalo completo é predeterminado entre 0 e 10.
Acabei de encontrar xrange () (surpreso por não ter visto isso antes ...) e o adicionei ao exemplo acima. xrange () é uma versão iterável do range () que tem a vantagem de não pré-construir a lista. Seria muito útil se você tivesse um corpus gigante de dados para iterar e tivesse apenas muita memória para fazer isso.
fonte
Vejo alguns de vocês fazendo
return self
em__iter__
. Eu só queria observar que__iter__
ele próprio pode ser um gerador (removendo assim a necessidade__next__
e criandoStopIteration
exceções)É claro que aqui é possível criar diretamente um gerador, mas para classes mais complexas, pode ser útil.
fonte
return self
em__iter__
. Quando eu tentava usáyield
-lo, encontrei seu código fazendo exatamente o que eu queria tentar.next()
?return iter(self).next()
?self.current
ou qualquer outro contador. Essa deve ser a resposta mais votada!iter
instâncias da classe, mas elas não são elas próprias instâncias da classe.Esta pergunta é sobre objetos iteráveis, não sobre iteradores. No Python, as seqüências também são iteráveis; portanto, uma maneira de criar uma classe iterável é fazê-la se comportar como uma sequência, ou seja, fornecer a ela
__getitem__
e__len__
métodos. Eu testei isso no Python 2 e 3.fonte
__len__()
método.__getitem__
sozinho com o comportamento esperado é suficiente.Todas as respostas nesta página são realmente ótimas para um objeto complexo. Mas para aqueles que contêm embutido tipos iteráveis como atributos, como
str
,list
,set
oudict
, ou em qualquer implementação decollections.Iterable
, você pode omitir certas coisas em sua classe.Pode ser usado como:
fonte
return iter(self.string)
.Esta é uma função iterável sem
yield
. Ele faz uso daiter
função e de um fechamento que mantém seu estado em um mutable (list
) no escopo do python 2.Para Python 3, o estado de fechamento é mantido em um imutável no escopo anexo e
nonlocal
é usado no escopo local para atualizar a variável de estado.Teste;
fonte
iter
, mas apenas para esclarecer: isso é mais complexo e menos eficiente do que apenas usar umayield
função geradora baseada; O Python possui um monte de suporte de intérprete parayield
funções de gerador baseadas das quais você não pode tirar proveito daqui, tornando esse código significativamente mais lento. Mesmo votado.Se você procura algo curto e simples, talvez seja o suficiente para você:
exemplo de uso:
fonte
Inspirado pela resposta de Matt Gregory, aqui está um iterador um pouco mais complicado que retornará a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
fonte