Cómo implementar __iter __ (self) para un objeto contenedor (Python)

115

He escrito un objeto contenedor personalizado.

De acuerdo con esta página , necesito implementar este método en mi objeto:

__iter__(self)

Sin embargo, al seguir el enlace a los tipos de iteradores en el manual de referencia de Python, no se dan ejemplos de cómo implementar el suyo.

¿Alguien puede publicar un fragmento (o un enlace a un recurso) que muestre cómo hacer esto?

El contenedor que estoy escribiendo es un mapa (es decir, almacena valores mediante claves únicas). Los dicts se pueden iterar así:

for k, v in mydict.items()

En este caso, necesito poder devolver dos elementos (¿una tupla?) En el iterador. Todavía no está claro cómo implementar dicho iterador (a pesar de las diversas respuestas que se han proporcionado amablemente). ¿Alguien podría arrojar algo más de luz sobre cómo implementar un iterador para un objeto contenedor similar a un mapa? (es decir, una clase personalizada que actúa como un dictado)?

skyeagle
fuente

Respuestas:

120

Normalmente usaría una función de generador. Cada vez que use una declaración de rendimiento, agregará un artículo a la secuencia.

Lo siguiente creará un iterador que arroje cinco, y luego cada elemento en some_list.

def __iter__(self):
   yield 5
   yield from some_list

Pre-3.3, yield fromno existía, por lo que tendría que hacer:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
Mikerobi
fuente
¿Es necesario aumentar StopIteration? ¿Cómo distingue esto cuándo parar?
Jonathan
1
@JonathanLeaders Cuando some_listse hayan obtenido todos los elementos de .
laike9m
En realidad, con este caso de uso, solo necesita aumentar StopIteration si desea dejar de generar valores antes de que some_list se agote.
Tim Peoples
21
StopIterationes generado automáticamente por Python cuando la función generadora regresa, ya sea al llamar explícitamente returno al llegar al final de la función (que como todas las funciones tiene un implícito return Noneal final). La elevación explícita StopIterationno es necesaria y, a partir de Python 3.5, en realidad no funcionará (consulte PEP 479 ): al igual que los generadores se convierten returnen StopIteration, se convierten explícitamente raise StopIterationen a RuntimeError.
Arthur Tacca
28

Otra opción es heredar de la clase base abstracta apropiada del módulo de colecciones como se documenta aquí .

En caso de que el contenedor sea su propio iterador, puede heredar de collections.Iterator. nextEntonces solo necesitas implementar el método.

Un ejemplo es:

>>> 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

Mientras mira el collectionsmódulo, considere heredar de Sequence, Mappingu otra clase base abstracta si es más apropiado. A continuación, se muestra un ejemplo de una Sequencesubclase:

>>> 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 : Gracias a Glenn Maynard por llamar mi atención sobre la necesidad de aclarar la diferencia entre los iteradores por un lado y los contenedores que son iterables en lugar de iteradores por el otro.

Muhammad Alkarouri
fuente
13
No mezcle objetos iterables e iteradores; no desea heredar de Iterator para un objeto iterable que no es un iterador en sí mismo (por ejemplo, un contenedor).
Glenn Maynard
@Glenn: Tiene razón en que un contenedor típico no es un iterador. Acabo de seguir la pregunta, que menciona los tipos de iteradores. Creo que es más apropiado heredar de una opción más adecuada como dije al final de la respuesta. Aclararé este punto en la respuesta.
Muhammad Alkarouri
13

por lo general, __iter__()solo devuelve self si ya has definido el método next () (objeto generador):

aquí hay un ejemplo ficticio de un generador:

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

pero __iter__()también se puede utilizar así: http://mail.python.org/pipermail/tutor/2006-January/044455.html

mouad
fuente
7
Eso es lo que hace con las clases de iterador, pero la pregunta es sobre objetos contenedores.
Glenn Maynard
11

Si su objeto contiene un conjunto de datos al que desea vincular el iter de su objeto, puede hacer trampa y hacer esto:

>>> 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
fuente
2
En lugar de verificar hasattr, usetry/except AttributeError
IceArdor
9

La "interfaz iterable" en Python consta de dos métodos __next__()y __iter__(). La __next__función es la más importante, ya que define el comportamiento del iterador, es decir, la función determina qué valor debe devolverse a continuación. El __iter__()método se utiliza para restablecer el punto de inicio de la iteración. A menudo, encontrará que __iter__()puede regresar a sí mismo cuando __init__()se usa para establecer el punto de partida.

Consulte el siguiente código para definir una clase inversa que implementa la "interfaz iterable" y define un iterador sobre cualquier instancia de cualquier clase de secuencia. El __next__()método comienza al final de la secuencia y devuelve valores en orden inverso a la secuencia. Tenga en cuenta que las instancias de una clase que implementa la "interfaz de secuencia" deben definir __len__()un __getitem__()método y un 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
fuente
7

Para responder a la pregunta sobre las asignaciones : lo que proporcionó __iter__debe iterar sobre las claves de la asignación. El siguiente es un ejemplo simple que crea un mapeo x -> x * xy funciona en Python3 extendiendo el mapeo 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
fuente
4

En caso de que no desee heredar dictcomo otros han sugerido, aquí hay una respuesta directa a la pregunta sobre cómo implementar __iter__para un ejemplo crudo de un dictado 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

Eso usa un generador, que está bien descrito aquí .

Dado que heredamos de Mapping, también debe implementar __getitem__y __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
fuente
2

Una opción que podría funcionar para algunos casos es hacer que su clase personalizada hereda de dict. Esto parece una elección lógica si actúa como un dict; tal vez debería ser un dict. De esta manera, obtiene una iteración similar a un dictado de forma gratuita.

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

Salida:

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

ejemplo para inhert de dict, modifique su iter, por ejemplo, salte la tecla 2cuando esté en el bucle 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
fuente