¿Qué son exactamente iterador, iterable e iteración?

442

¿Cuál es la definición más básica de "iterable", "iterador" e "iteración" en Python?

He leído varias definiciones pero no puedo identificar el significado exacto, ya que todavía no se asimilará.

¿Puede alguien ayudarme con las 3 definiciones en términos simples?

thechrishaddad
fuente

Respuestas:

530

La iteración es un término general para tomar cada elemento de algo, uno tras otro. Cada vez que usa un bucle, explícito o implícito, para repasar un grupo de elementos, eso es iteración.

En Python, iterable e iterator tienen significados específicos.

Un iterable es un objeto que tiene un __iter__método que devuelve un iterador , o que define un __getitem__método que puede tomar índices secuenciales a partir de cero (y aumenta IndexErrorcuando los índices ya no son válidos). Entonces, un iterable es un objeto del que puede obtener un iterador .

Un iterador es un objeto con un método next(Python 2) o __next__(Python 3).

Siempre que use un forbucle, o map, o una comprensión de lista, etc. en Python, el nextmétodo se llama automáticamente para obtener cada elemento del iterador , pasando así por el proceso de iteración .

Un buen lugar para comenzar a aprender sería la sección de iteradores del tutorial y la sección de tipos de iteradores de la página de tipos estándar . Después de comprender los conceptos básicos, pruebe la sección de iteradores del CÓMO de programación funcional .

agf
fuente
1
Tenga en cuenta que las collections.abc.AsyncIteratorpruebas __aiter__y los __anext__métodos. Esta es una nueva adición en 3.6.
Janus Troelsen
1
@jlh ¿por qué estaría __len__necesariamente vinculado a la iteración? ¿Cómo le ayudaría saber la longitud de algo?
shadowtalker
2
@shadowtalker ayudaría a saber qué índices son válidos, para que sepa con qué índices se pueden usar __getitem__.
jlh
44
@jlh parece que estás proponiendo un comportamiento dfeault muy obstinado. Considere que {'a': 'hi', 'b': 'bye'}tiene una longitud de 2, pero no puede indexarse ​​por 0, 1 o 2.
shadowtalker
2
@shadowtalker. Pero un dict tiene un __iter__método. Creo que jlh se refiere a objetos que son iterables específicamente porque definen: "un __getitem__método que puede tomar índices secuenciales a partir de cero".
Rico
337

Aquí está la explicación que uso al enseñar clases de Python:

Un ITERABLE es:

  • cualquier cosa que se pueda recorrer (es decir, puede recorrer una cadena o archivo) o
  • cualquier cosa que pueda aparecer en el lado derecho de un ciclo for: for x in iterable: ...o
  • cualquier cosa que pueda llamar con iter()eso devolverá un ITERATOR: iter(obj)o
  • un objeto que define __iter__que devuelve un ITERATOR nuevo, o puede tener un __getitem__método adecuado para la búsqueda indexada.

Un ITERATOR es un objeto:

  • con estado que recuerda dónde está durante la iteración,
  • con un __next__método que:
    • devuelve el siguiente valor en la iteración
    • actualiza el estado para señalar el siguiente valor
    • señales cuando se hace elevando StopIteration
  • y eso es auto iterable (lo que significa que tiene un __iter__método que devuelve self).

Notas:

  • El __next__método en Python 3 se deletrea nexten Python 2, y
  • La función incorporada next()llama a ese método en el objeto que se le pasa.

Por ejemplo:

>>> s = 'cat'      # s is an ITERABLE
                   # s is a str object that is immutable
                   # s has no state
                   # s has a __getitem__() method 

>>> t = iter(s)    # t is an ITERATOR
                   # t has state (it starts by pointing at the "c"
                   # t has a next() method and an __iter__() method

>>> next(t)        # the next() function returns the next value and advances the state
'c'
>>> next(t)        # the next() function returns the next value and advances
'a'
>>> next(t)        # the next() function returns the next value and advances
't'
>>> next(t)        # next() raises StopIteration to signal that iteration is complete
Traceback (most recent call last):
...
StopIteration

>>> iter(t) is t   # the iterator is self-iterable
Raymond Hettinger
fuente
¿Qué quieres decir con iterador nuevo?
lmiguelvargasf
13
@lmiguelvargasf "Fresco" como en "nuevo y no consumido" en lugar de "agotado o parcialmente consumido". La idea es que un nuevo iterador comience desde el principio, mientras que un iterador parcialmente usado continúa donde lo dejó.
Raymond Hettinger
Sus viñetas segunda, tercera y cuarta indican claramente lo que quiere decir, en términos de construcciones de python específicas o incorporados o llamadas a métodos. Pero la primera viñeta ("cualquier cosa que se pueda pasar") no tiene esa claridad. Además, la primera viñeta parece tener una superposición con la segunda viñeta, ya que la segunda viñeta trata de forbucles, y la primera viñeta trata de "hacer un bucle". ¿Podría por favor abordar estos?
fuente
2
Considere por favor reformular "cualquier cosa con la que pueda llamar iter()" como "cualquier cosa a la que pueda pasar iter()"
fuente
98

Las respuestas anteriores son geniales, pero como la mayoría de lo que he visto, no enfatice la distinción lo suficiente para personas como yo.

Además, las personas tienden a ponerse "demasiado pitónicas" al poner definiciones como "X es un objeto que tiene __foo__()método" antes. Dichas definiciones son correctas: se basan en la filosofía de tipear patos, pero el enfoque en los métodos tiende a interponerse cuando se trata de comprender el concepto en su simplicidad.

Entonces agrego mi versión.


En lenguaje natural,

  • La iteración es el proceso de tomar un elemento a la vez en una fila de elementos.

En Python

  • iterable es un objeto que es, bueno, iterable, lo que simplemente significa que puede usarse en iteración, por ejemplo, con un forbucle. ¿Cómo? Mediante el uso de iterador . Te lo explicaré a continuación.

  • ... mientras que el iterador es un objeto que define cómo hacer la iteración, específicamente cuál es el siguiente elemento. Por eso debe tener next()método.

Los iteradores también son iterables, con la distinción de que su __iter__()método devuelve el mismo objeto ( self), independientemente de si sus elementos han sido consumidos o no por llamadas anteriores a next().


Entonces, ¿qué piensa el intérprete de Python cuando ve una for x in obj:declaración?

Mira, un forbucle. Parece un trabajo para un iterador ... Consigamos uno. ... Está este objtipo, así que preguntémosle.

"Sr. obj, ¿tiene su iterador?" (... llamadas iter(obj), llamadas obj.__iter__(), que felizmente reparte un nuevo iterador brillante _i).

OK, eso fue fácil ... Comencemos iterando entonces. ( x = _i.next()... x = _i.next()...)

Como Mr. objtuvo éxito en esta prueba (al hacer que cierto método devuelva un iterador válido), lo recompensamos con un adjetivo: ahora puede llamarlo "Mr. iterable obj".

Sin embargo, en casos simples, normalmente no se beneficia de tener iterador e iterable por separado. Por lo tanto, define solo un objeto, que también es su propio iterador. (A Python realmente no le importa que lo _ientregado objno fuera tan brillante, sino solo el objmismo).

Es por eso que en la mayoría de los ejemplos que he visto (y lo que me había estado confundiendo una y otra vez), puede ver:

class IterableExample(object):

    def __iter__(self):
        return self

    def next(self):
        pass

en vez de

class Iterator(object):
    def next(self):
        pass

class Iterable(object):
    def __iter__(self):
        return Iterator()

Sin embargo, hay casos en los que puede beneficiarse de tener un iterador separado del iterable, como cuando desea tener una fila de elementos, pero más "cursores". Por ejemplo, cuando desea trabajar con elementos "actuales" y "próximos", puede tener iteradores separados para ambos. O múltiples hilos que se extraen de una gran lista: cada uno puede tener su propio iterador para recorrer todos los elementos. Ver las respuestas de @ Raymond y @ glglgl arriba.

Imagina lo que podrías hacer:

class SmartIterableExample(object):

    def create_iterator(self):
        # An amazingly powerful yet simple way to create arbitrary
        # iterator, utilizing object state (or not, if you are fan
        # of functional), magic and nuclear waste--no kittens hurt.
        pass    # don't forget to add the next() method

    def __iter__(self):
        return self.create_iterator()

Notas:

  • Repetiré nuevamente: el iterador no es iterable . Iterator no se puede utilizar como "fuente" en forbucle. Lo que el forbucle necesita principalmente es __iter__() (que devuelve algo con next()).

  • Por supuesto, forno es el único ciclo de iteración, por lo que lo anterior también se aplica a algunas otras construcciones ( while...).

  • Los iteradores next()pueden lanzar StopIteration para detener la iteración. Sin embargo, no tiene que hacerlo, puede iterar para siempre o usar otros medios.

  • En el "proceso de pensamiento" anterior, _irealmente no existe. He inventado ese nombre.

  • Hay un pequeño cambio en Python 3.x: next()ahora se debe invocar el método (no el incorporado) __next__(). Sí, debería haber sido así todo el tiempo.

  • También puede pensarlo así: iterable tiene los datos, el iterador extrae el siguiente elemento

Descargo de responsabilidad: no soy desarrollador de ningún intérprete de Python, por lo que realmente no sé qué "piensa" el intérprete. Las reflexiones anteriores son únicamente una demostración de cómo entiendo el tema a partir de otras explicaciones, experimentos y experiencias de la vida real de un novato en Python.

Alois Mahdal
fuente
1
Esto es genial, pero todavía estoy un poco confundido. Pensé que su casilla amarilla decía que un forbucle necesita un iterador ("Mire, un bucle for. Parece un trabajo para un iterador ... Consigamos uno"). Pero luego dices en las notas al final que "Iterator no se puede usar como fuente en un forbucle" ...?
Racing Tadpole
¿Por qué pones solo passen el código para esas nextdefiniciones? Asumiré que solo quiere decir que alguien tiene que implementar una forma de obtener el siguiente, ya que el siguiente tiene que devolver algo.
nealmcb
@nealmcb Sí, creo que eso fue lo que me quiso decir. (Para eso passes , después de todo.)
Alois Mahdal
@AloisMahdal Ahh, no había visto ese uso antes. Cuando veo pass, creo que está ahí por razones sintácticas. Acabo de encontrar las respuestas en el objeto de puntos suspensivos que son bastante interesantes: puede usar ...para indicar un bloque "todo más tarde". NotImplementedTambién está disponible.
nealmcb
Si bien me gusta que estés haciendo hincapié en la distinción entre un iterador y un iterable, esta respuesta se contradice. Primero escribe: 'Los iteradores también son iterables' (que coincide con lo que está escrito en la documentación de Python ). Pero luego escribes: ' iterador no es iterable . Iterator no se puede utilizar como "fuente" en forbucle '. Recibo el punto de su respuesta, y me gusta de otra manera, pero creo que sería beneficioso arreglar esto.
Rico
22

Un iterable es un objeto que tiene un __iter__()método. Posiblemente puede repetirse varias veces, como list()s y tuple()s.

Un iterador es el objeto que itera. Se devuelve mediante un __iter__()método, se devuelve mediante su propio __iter__()método y tiene un next()método ( __next__()en 3.x).

La iteración es el proceso de llamar a esto next()resp. __next__()hasta que suba StopIteration.

Ejemplo:

>>> a = [1, 2, 3] # iterable
>>> b1 = iter(a) # iterator 1
>>> b2 = iter(a) # iterator 2, independent of b1
>>> next(b1)
1
>>> next(b1)
2
>>> next(b2) # start over, as it is the first call to b2
1
>>> next(b1)
3
>>> next(b1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> b1 = iter(a) # new one, start over
>>> next(b1)
1
glglgl
fuente
¿Entonces realmente es solo un objeto que pasa a través del contenedor? ¿Sería esto útil?
thechrishaddad
A menudo, pero no siempre. Un generador, archivo o cursor de base de datos solo se puede iterar una vez y, por lo tanto, son sus propios iteradores.
glglgl
Supongo que b2 no tiene que ser independiente de b1? para este caso especial, es independiente, seguro que puedo hacerlo no independiente sino también válido Iterable.
Bin
@Bin Sí. Como y Iteratores siempre Iterabley es propio Iterator, dos llamadas de iter()no necesariamente dan dos Iterators independientes .
glglgl
13

Aquí está mi hoja de trucos:

 sequence
  +
  |
  v
   def __getitem__(self, index: int):
  +    ...
  |    raise IndexError
  |
  |
  |              def __iter__(self):
  |             +     ...
  |             |     return <iterator>
  |             |
  |             |
  +--> or <-----+        def __next__(self):
       +        |       +    ...
       |        |       |    raise StopIteration
       v        |       |
    iterable    |       |
           +    |       |
           |    |       v
           |    +----> and +-------> iterator
           |                               ^
           v                               |
   iter(<iterable>) +----------------------+
                                           |
   def generator():                        |
  +    yield 1                             |
  |                 generator_expression +-+
  |                                        |
  +-> generator() +-> generator_iterator +-+

Prueba: ¿Ves cómo ...

  1. cada iterador es un iterable?
  2. ¿El __iter__()método de un objeto contenedor se puede implementar como generador?
  3. Un iterable que tiene un __next__método no es necesariamente un iterador?

Respuestas:

  1. Cada iterador debe tener un __iter__método. Tener __iter__es suficiente para ser iterable. Por lo tanto, cada iterador es iterable.
  2. Cuando __iter__ se llama, debe devolver un iterador ( return <iterator>en el diagrama anterior). Llamar a un generador devuelve un iterador generador que es un tipo de iterador.

    class Iterable1:
        def __iter__(self):
            # a method (which is a function defined inside a class body)
            # calling iter() converts iterable (tuple) to iterator
            return iter((1,2,3))
    
    class Iterable2:
        def __iter__(self):
            # a generator
            for i in (1, 2, 3):
                yield i
    
    class Iterable3:
        def __iter__(self):
            # with PEP 380 syntax
            yield from (1, 2, 3)
    
    # passes
    assert list(Iterable1()) == list(Iterable2()) == list(Iterable3()) == [1, 2, 3]
  3. Aquí hay un ejemplo:

    class MyIterable:
    
        def __init__(self):
            self.n = 0
    
        def __getitem__(self, index: int):
            return (1, 2, 3)[index]
    
        def __next__(self):
            n = self.n = self.n + 1
            if n > 3:
                raise StopIteration
            return n
    
    # if you can iter it without raising a TypeError, then it's an iterable.
    iter(MyIterable())
    
    # but obviously `MyIterable()` is not an iterator since it does not have
    # an `__iter__` method.
    from collections.abc import Iterator
    assert isinstance(MyIterable(), Iterator)  # AssertionError
AXO
fuente
1
En el cuestionario, entendí solo el primer punto. es decir, el iterador se convierte en un iterable ya que tiene un __iter__método. ¿Puede explicar los puntos segundo y tercero editando esta respuesta
AnV
@AnV: Hasta donde yo entiendo: re 2 .: __iter__()devuelve un iterador. Un generador es un iterador, por lo que puede usarse para este propósito. re 3 .: Solo puedo adivinar aquí, pero creo que si __iter__()falta o no regresa self, no es un iterador, porque el iterador __iter__()tiene que regresar self.
glglgl
10

No sé si ayuda a alguien, pero siempre me gusta visualizar conceptos en mi cabeza para comprenderlos mejor. Entonces, como tengo un hijo pequeño, visualizo el concepto iterable / iterador con ladrillos y papel blanco.

Supongamos que estamos en el cuarto oscuro y en el piso tenemos ladrillos para mi hijo. Ladrillos de diferente tamaño, color, no importa ahora. Supongamos que tenemos 5 ladrillos como esos. Esos 5 ladrillos se pueden describir como un objeto , digamos kit de ladrillos . Podemos hacer muchas cosas con este kit de ladrillos: puede tomar uno y luego tomar el segundo y luego el tercero, puede cambiar los lugares de los ladrillos, colocar el primer ladrillo por encima del segundo. Podemos hacer muchas cosas con ellos. Por lo tanto, este kit de ladrillos es un objeto o secuencia iterable , ya que podemos atravesar cada ladrillo y hacer algo con él. Solo podemos hacerlo como mi pequeño hijo: podemos jugar con un ladrillo iterable . a la vez . Así que de nuevo me imagino que este kit de ladrillos es un

Ahora recuerda que estamos en el cuarto oscuro. O casi oscuro. La cuestión es que no vemos claramente esos ladrillos, de qué color son, qué forma, etc. Por lo tanto, incluso si queremos hacer algo con ellos, es decir, iterar a través de ellos , realmente no sabemos qué y cómo porque es demasiado oscuro.

Lo que podemos hacer es cerca del primer ladrillo, como elemento de un kit de ladrillos, podemos poner un trozo de papel fluorescente blanco para que podamos ver dónde está el primer elemento de ladrillo. Y cada vez que sacamos un ladrillo de un kit, reemplazamos el trozo de papel blanco por otro ladrillo para poder verlo en el cuarto oscuro. Este papel blanco no es más que un iterador . Es un objeto también . Pero un objeto con el que podemos trabajar y jugar con elementos de nuestro objeto iterable: kit de ladrillos.

Eso, por cierto, explica mi error inicial cuando intenté lo siguiente en un IDLE y obtuve un TypeError:

 >>> X = [1,2,3,4,5]
 >>> next(X)
 Traceback (most recent call last):
    File "<pyshell#19>", line 1, in <module>
      next(X)
 TypeError: 'list' object is not an iterator

La Lista X aquí fue nuestro kit de ladrillos, pero NO un trozo de papel blanco. Primero necesitaba encontrar un iterador:

>>> X = [1,2,3,4,5]
>>> bricks_kit = [1,2,3,4,5]
>>> white_piece_of_paper = iter(bricks_kit)
>>> next(white_piece_of_paper)
1
>>> next(white_piece_of_paper)
2
>>>

No sé si ayuda, pero me ayudó. Si alguien pudiera confirmar / corregir la visualización del concepto, estaría agradecido. Me ayudaría a aprender más.

Nikolay Dudaev
fuente
6

Iterable : - algo que es iterable es iterable; como secuencias como listas, cadenas, etc. También tiene el __getitem__método o un __iter__método. Ahora, si usamos la iter()función en ese objeto, obtendremos un iterador.

Iterador : - Cuando obtenemos el objeto iterador de la iter()función; Llamamos __next__()método (en python3) o simplemente next()(en python2) para obtener elementos uno por uno. Esta clase o instancia de esta clase se llama iterador.

De documentos: -

El uso de iteradores impregna y unifica Python. Detrás de escena, la instrucción for invoca  iter() el objeto contenedor. La función devuelve un objeto iterador que define el método  __next__() que accede a los elementos en el contenedor de uno en uno. Cuando no hay más elementos,  __next__() genera una excepción StopIteration que le indica al ciclo for que finalice. Puede llamar al  __next__() método utilizando la  next() función incorporada; Este ejemplo muestra cómo funciona todo:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Ex de una clase: -

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    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')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s
Vicrobot
fuente
4

No creo que pueda hacerlo mucho más simple que la documentación , sin embargo, intentaré:

  • Iterable es algo que se puede repetir . En la práctica, generalmente significa una secuencia, por ejemplo, algo que tiene un principio y un final y una forma de recorrer todos los elementos que contiene.
  • Puede pensar Iterator como un pseudo-método auxiliar (o pseudo-atributo) que le da (o mantiene) el siguiente (o primer) elemento en el iterable . (En la práctica, es solo un objeto que define el método next())

  • La iteración probablemente se explica mejor por la definición de la palabra Merriam-Webster :

b: la repetición de una secuencia de instrucciones de la computadora un número específico de veces o hasta que se cumpla una condición - compare la recursividad

Kimvais
fuente
3
iterable = [1, 2] 

iterator = iter(iterable)

print(iterator.__next__())   

print(iterator.__next__())   

entonces,

  1. iterablees un objeto que se puede recorrer . por ejemplo, lista, cadena, tupla, etc.

  2. El uso de la iterfunción en nuestro iterableobjeto devolverá un objeto iterador.

  3. ahora este objeto iterador tiene un método llamado __next__(en Python 3, o solo nexten Python 2) por el cual puede acceder a cada elemento de iterable.

entonces, LA PRODUCCIÓN DEL CÓDIGO ANTERIOR SERÁ:

1

2

Arpan Kumar
fuente
3

Los Iterables tienen un __iter__método que crea instancias de un nuevo iterador cada vez.

Los iteradores implementan un __next__método que devuelve elementos individuales y un __iter__método que devuelve self.

Por lo tanto, los iteradores también son iterables, pero los iterables no son iteradores.

Luciano Ramalho, pitón fluido.

techkuz
fuente
2

Antes de tratar con los iterables y el iterador, el factor principal que decide el iterable y el iterador es la secuencia

Secuencia: Secuencia es la recopilación de datos.

Iterable: Iterable son los objetos de tipo secuencia que admiten el __iter__método.

Método Iter: el método Iter toma la secuencia como entrada y crea un objeto que se conoce como iterador

Iterator: Iterator es el objeto que llama al siguiente método y atraviesa la secuencia. Al llamar al siguiente método, devuelve el objeto que atravesó actualmente.

ejemplo:

x=[1,2,3,4]

x es una secuencia que consiste en la recopilación de datos

y=iter(x)

Al llamarlo iter(x), devuelve un iterador solo cuando el objeto x tiene un método iter; de lo contrario, genera una excepción. Si devuelve un iterador, y se asigna así:

y=[1,2,3,4]

Como y es un iterador, por lo tanto, admite el next()método

Al llamar al siguiente método, devuelve los elementos individuales de la lista uno por uno.

Después de devolver el último elemento de la secuencia, si llamamos nuevamente al siguiente método, se genera un error StopIteration

ejemplo:

>>> y.next()
1
>>> y.next()
2
>>> y.next()
3
>>> y.next()
4
>>> y.next()
StopIteration
Sombra
fuente
Solo una observación: y = iter (x) no es exactamente y = [1,2,3,4] ya que y ahora es un objeto iterador. Quizás debería agregar un comentario para aclarar que no es una lista sino un objeto iterador o cambiar la representación.
coelhudo
-6

En Python todo es un objeto. Cuando se dice que un objeto es iterable, significa que puede recorrer (es decir, iterar) el objeto como una colección.

Las matrices, por ejemplo, son iterables. Puede recorrerlos con un bucle for y pasar del índice 0 al índice n, siendo n la longitud del objeto de matriz menos 1.

Los diccionarios (pares de clave / valor, también llamados matrices asociativas) también son iterables. Puedes pasar por sus llaves.

Obviamente, los objetos que no son colecciones no son iterables. Un objeto bool, por ejemplo, solo tiene un valor, verdadero o falso. No es iterable (no tendría sentido que sea un objeto iterable).

Lee mas. http://www.lepus.org.uk/ref/companion/Iterator.xml

usuario93097373
fuente
66
los objetos que no son colecciones no son iterables, generalmente no es cierto. Para dar solo un par de ejemplos, los generadores son iterables pero no son colecciones, y los objetos iteradores creados al invocar iter()los tipos de colección estándar son iterables pero no son, en sí mismos, colecciones.
Mark Amery