Como implementar __iter __ (self) para um objeto container (Python)

115

Eu escrevi um objeto de recipiente personalizado.

De acordo com esta página , preciso implementar este método em meu objeto:

__iter__(self)

No entanto, ao seguir o link para Tipos de Iterador no manual de referência do Python, não há exemplos fornecidos de como implementar o seu próprio.

Alguém pode postar um snippet (ou link para um recurso), que mostra como fazer isso?

O contêiner que estou escrevendo é um mapa (ou seja, armazena valores por chaves exclusivas). dictos podem ser iterados assim:

for k, v in mydict.items()

Nesse caso, preciso retornar dois elementos (uma tupla?) No iterador. Ainda não está claro como implementar tal iterador (apesar das várias respostas que foram gentilmente fornecidas). Alguém poderia lançar mais luz sobre como implementar um iterador para um objeto recipiente semelhante a um mapa? (ou seja, uma classe personalizada que atua como um dicionário)?

skyeagle
fonte

Respostas:

120

Eu normalmente usaria uma função de gerador. Cada vez que você usar um demonstrativo de rendimento, ele adicionará um item à sequência.

O seguinte criará um iterador que produz cinco e, em seguida, todos os itens em some_list.

def __iter__(self):
   yield 5
   yield from some_list

Pré-3.3, yield fromnão existia, então você teria que fazer:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
mikerobi
fonte
É necessário aumentar StopIteration? Como isso distingue quando parar?
Jonathan
1
@JonathanLeaders Quando todos os elementos em some_listtiverem sido produzidos.
laike9m
Na verdade - com este caso de uso - você só precisa aumentar StopIteration se desejar parar de gerar valores antes de some_list se esgotar.
Tim Peoples
21
StopIterationé gerado automaticamente pelo Python quando a função geradora retorna, seja por você chamando explicitamente returnou alcançando o final da função (que, como todas as funções, tem um return Nonefinal implícito ). Aumentar explicitamente StopIterationnão é necessário e a partir do Python 3.5 realmente não funcionará (consulte PEP 479 ): assim como os geradores se transformam returnem StopIteration, eles se tornam explícitos raise StopIterationem a RuntimeError.
Arthur Tacca
28

Outra opção é herdar da classe base abstrata apropriada do módulo `coleções como documentado aqui .

Caso o contêiner seja seu próprio iterador, você pode herdar de collections.Iterator. Você só precisa implementar o nextmétodo então.

Um exemplo é:

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

Enquanto você está olhando para o collectionsmódulo, considere herdar de Sequence, Mappingou outra classe base abstrata, se for mais apropriado. Aqui está um exemplo para uma Sequencesubclasse:

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB : Obrigado a Glenn Maynard por chamar minha atenção para a necessidade de esclarecer a diferença entre iteradores por um lado e contêineres que são iteráveis ​​em vez de iteradores por outro.

Muhammad Alkarouri
fonte
13
Não misture objetos iteráveis ​​e iteradores - você não quer herdar de Iterator um objeto iterável que não seja um iterador em si (por exemplo, um contêiner).
Glenn Maynard
@Glenn: Você está correto ao dizer que um contêiner típico não é um iterador. Eu apenas segui a pergunta, que menciona os tipos de iterador. Acho mais adequado herdar de uma opção mais adequada como disse no final da resposta. Vou esclarecer esse ponto na resposta.
Muhammad Alkarouri
13

normalmente __iter__()apenas retorna self se você já definiu o método next () (objeto gerador):

aqui está um exemplo fictício de um gerador:

class Test(object):

    def __init__(self, data):
       self.data = data

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

mas __iter__()também pode ser usado assim: http://mail.python.org/pipermail/tutor/2006-January/044455.html

muad
fonte
7
Isso é o que você faz para classes iteradoras, mas a questão é sobre objetos de contêiner.
Glenn Maynard
11

Se o seu objeto contém um conjunto de dados ao qual deseja vincular o iter do seu objeto, você pode trapacear e fazer o seguinte:

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6
Squirrelsama
fonte
2
Em vez de verificar hasattr, usetry/except AttributeError
IceArdor de
10

A "interface iterável" em python consiste em dois métodos __next__()e __iter__(). A __next__função é a mais importante, pois define o comportamento do iterador - ou seja, a função determina qual valor deve ser retornado em seguida. O __iter__()método é usado para redefinir o ponto de partida da iteração. Freqüentemente, você descobrirá que __iter__()só pode retornar self quando __init__()for usado para definir o ponto de partida.

Consulte o código a seguir para definir uma Class Reverse que implementa a "interface iterável" e define um iterador sobre qualquer instância de qualquer classe de sequência. O __next__()método começa no final da sequência e retorna valores na ordem inversa da sequência. Observe que as instâncias de uma classe que implementa a "interface de sequência" devem definir um __len__()e um __getitem__()método.

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, seq):
        self.data = seq
        self.index = len(seq)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> next(rev)   # note no need to call iter()
'm'
>>> nums = Reverse(range(1,10))
>>> next(nums)
9
FredAKA
fonte
7

Para responder à pergunta sobre mapeamentos : seu fornecido __iter__deve iterar sobre as chaves do mapeamento. A seguir está um exemplo simples que cria um mapeamento x -> x * xe funciona em Python3 estendendo o mapeamento ABC.

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16
Juan A. Navarro
fonte
4

Caso você não queira herdar dictcomo outros sugeriram, aqui está uma resposta direta à pergunta sobre como implementar __iter__um exemplo bruto de um dicionário personalizado:

class Attribute:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class Node(collections.Mapping):
    def __init__(self):
        self.type  = ""
        self.attrs = [] # List of Attributes

    def __iter__(self):
        for attr in self.attrs:
            yield attr.key

Isso usa um gerador, que é bem descrito aqui .

Já que estamos herdando de Mapping, você também precisa implementar __getitem__e __len__:

    def __getitem__(self, key):
        for attr in self.attrs:
            if key == attr.key:
                return attr.value
        raise KeyError

    def __len__(self):
        return len(self.attrs)
jfritz42
fonte
2

Uma opção que pode funcionar em alguns casos é fazer com que sua classe personalizada herde de dict. Esta parece ser uma escolha lógica se agir como um ditado; talvez devesse ser um ditado. Dessa forma, você obtém uma iteração do tipo dict gratuitamente.

class MyDict(dict):
    def __init__(self, custom_attribute):
        self.bar = custom_attribute

mydict = MyDict('Some name')
mydict['a'] = 1
mydict['b'] = 2

print mydict.bar
for k, v in mydict.items():
    print k, '=>', v

Resultado:

Some name
a => 1
b => 2
Edificado
fonte
2

exemplo para herdar de dict, modifique a sua iter, por exemplo, tecla pular 2quando em loop for

# method 1
class Dict(dict):
    def __iter__(self):
        keys = self.keys()
        for i in keys:
            if i == 2:
                continue
            yield i

# method 2
class Dict(dict):
    def __iter__(self):
        for i in super(Dict, self).__iter__():
            if i == 2:
                continue
            yield i
WeizhongTu
fonte