Los iteradores son objetos sobre los que se puede iterar. Son una característica habitual del lenguaje Python, muy útil para bucles y comprensiones de listas. Cualquier objeto que pueda generar un iterador se denomina iterable.
Crear un iterador conlleva bastante trabajo. Por ejemplo, la implementación de cada objeto iterador debe incluir los métodos __iter__() y __next__(). Además de este requisito, la implementación debe llevar un seguimiento del estado interno del objeto y lanzar una excepción StopIteration cuando ya no queden valores que devolver. Estas reglas se conocen como el protocolo de iteradores.
Implementar tu propio iterador es un proceso largo y no siempre necesario. Una alternativa más sencilla es usar un generador. Los generadores son un tipo especial de función que utiliza la palabra clave yield para devolver un iterador que se puede recorrer, un valor cada vez.
Saber identificar cuándo conviene implementar un iterador o usar un generador mejorará tus habilidades como programador de Python. En el resto de este tutorial, destacaremos las diferencias entre ambos para ayudarte a elegir la mejor opción en cada caso.
Glosario
Término
Definición
Iterable
Objeto de Python que se puede recorrer en un bucle. Ejemplos de iterables: listas, conjuntos, tuplas, diccionarios, cadenas, etc.
Iterador
Objeto sobre el que se puede iterar. Por tanto, los iteradores contienen un número contable de valores.
Generador
Tipo especial de función que no devuelve un único valor: devuelve un objeto iterador con una secuencia de valores.
Evaluación perezosa
Estrategia de evaluación por la que ciertos objetos solo se generan cuando se necesitan. Por ello, en algunos círculos de desarrollo también se conoce como «call-by-need».
Protocolo de iteradores
Conjunto de reglas que se deben seguir para definir un iterador en Python.
next()
Función integrada que devuelve el siguiente elemento de un iterador.
iter()
Función integrada que convierte un iterable en un iterador.
yield()
Palabra clave de Python similar a return, salvo que yield devuelve un objeto generador en lugar de un valor.
Iteradores e iterables en Python
Los iterables son objetos capaces de devolver sus elementos uno a uno: se pueden recorrer. Las estructuras de datos integradas populares de Python, como listas, tuplas y conjuntos, son iterables. Otras estructuras como las cadenas y los diccionarios también lo son: una cadena puede iterarse carácter a carácter, y las claves de un diccionario se pueden recorrer. Como regla general, considera iterable a cualquier objeto que puedas usar en un bucle for.
Explorar iterables de Python con ejemplos
Dado lo anterior, podemos concluir que todos los iteradores también son iterables. Sin embargo, no todo iterable es necesariamente un iterador. Un iterable solo produce un iterador cuando se itera sobre él.
Para demostrarlo, vamos a instanciar una lista, que es un iterable, y a obtener un iterador llamando a la función integrada iter() sobre la lista.
list_instance =[1,2,3,4]print(iter(list_instance))"""
<list_iterator object at 0x7fd946309e90>
"""Con la tecnología de
Aunque la lista por sí sola no es un iterador, al llamar a iter() se convierte en un iterador y se devuelve el objeto iterador.
Para demostrar que no todos los iterables son iteradores, vamos a crear la misma lista e intentar llamar a la función next(), que devuelve el siguiente elemento de un iterador.
list_instance =[1,2,3,4]print(next(list_instance))"""
--------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-0cb076ed2d65> in <module>()
3 print(iter(list_instance))
4
----> 5 print(next(list_instance))
TypeError: 'list' object is not an iterator
"""Con la tecnología de
En el código anterior, puedes ver que intentar llamar a next() sobre la lista lanza un TypeError – aprende más sobre excepciones y manejo de errores en Python. Esto ocurre porque una lista es un iterable, no un iterador.
Explorar iteradores de Python con ejemplos
Por tanto, si el objetivo es iterar una lista, primero hay que obtener un objeto iterador. Solo entonces podremos avanzar por los valores de la lista.
# instanciar una lista
list_instance =[1,2,3,4]# convertir la lista en iterador
iterator =iter(list_instance)# devolver elementos uno a unoprint(next(iterator))print(next(iterator))print(next(iterator))print(next(iterator))"""
1
2
3
4
"""Con la tecnología de
Python crea automáticamente un objeto iterador cuando intentas recorrer un objeto iterable.
# instanciar una lista
list_instance =[1,2,3,4]# recorrer la listafor item in list_instance:print(item)"""
1
2
3
4
"""Con la tecnología de
Cuando se captura la excepción StopIteration, el bucle termina.
Los valores de un iterador solo se pueden recuperar de izquierda a derecha. Python no tiene una función previous() que permita retroceder en un iterador.
La naturaleza perezosa de los iteradores
Es posible definir varios iteradores basados en el mismo objeto iterable. Cada iterador mantiene su propio estado de progreso. Así, si defines varias instancias de iterador de un mismo iterable, puedes llegar al final con una mientras otra sigue al principio.
Observa que iterator_b imprime el primer elemento de la serie.
Podemos decir que los iteradores tienen una naturaleza perezosa: cuando se crea un iterador, los elementos no se producen hasta que se solicitan. Es decir, los elementos de nuestra lista solo se devolverán cuando los pidamos explícitamente con next(iter(list_instance)).
No obstante, también puedes extraer todos los valores de un iterador de una vez llamando a un contenedor iterable integrado (p. ej., list(), set(), tuple()) sobre el objeto iterador para forzar que genere todos sus elementos a la vez.
# instanciar iterable
list_instance =[1,2,3,4]# producir un iterador a partir de un iterable
iterator =iter(list_instance)print(list(iterator))"""
[1, 2, 3, 4]
"""Con la tecnología de
No es recomendable con iteradores grandes porque obliga a generar todos los elementos y mantenerlos en memoria a la vez, lo que va en contra de la evaluación perezosa.
Cuando un conjunto de datos es demasiado grande para caber cómodamente en memoria, o cuando quieres iteración perezosa sin escribir una clase de iterador completa, un generador suele encajar mejor.
Generadores en Python
La alternativa más ágil a implementar un iterador es usar un generador. Aunque los generadores se parecen a las funciones de Python normales, no son lo mismo. Para empezar, un generador no devuelve elementos como tal. En su lugar, utiliza la palabra clave yield para generarlos sobre la marcha. Por eso decimos que un generador es una función especial que se apoya en la evaluación perezosa.
Los generadores no almacenan su contenido en memoria como lo haría un iterable típico. Por ejemplo, si quisiéramos calcular todos los divisores de un entero positivo, normalmente escribiríamos una función tradicional (puedes aprender más sobre las funciones en Python en este tutorial) así:
deffactors(n):
factor_list =[]for val inrange(1, n+1):if n % val ==0:
factor_list.append(val)return factor_list
print(factors(20))"""
[1, 2, 4, 5, 10, 20]
"""Con la tecnología de
El código anterior devuelve la lista completa de divisores. Sin embargo, observa la diferencia cuando usamos un generador en lugar de una función tradicional de Python:
deffactors(n):for val inrange(1, n+1):if n % val ==0:yield val
print(factors(20))"""
<generator object factors at 0x7fd938271350>
"""Con la tecnología de
Como hemos usado yield en lugar de return, la función no termina tras ejecutarse. En esencia, le hemos dicho a Python que cree un objeto generador en lugar de una función tradicional, lo que permite mantener el estado del generador.
En consecuencia, es posible llamar a next() sobre el iterador perezoso para obtener los elementos de la serie uno a uno.
deffactors(n):for val inrange(1, n+1):if n % val ==0:yield val
factors_of_20 = factors(20)print(next(factors_of_20))"""
1
"""Con la tecnología de
Otra forma de crear un generador es con una comprensión de generador. Las expresiones generadoras usan una sintaxis muy similar a la de las comprensiones de listas, pero con paréntesis en lugar de corchetes.
factor_gen =(val for val inrange(1,21)if20% val ==0)print(list(factor_gen))"""
[1, 2, 4, 5, 10, 20]
"""Con la tecnología de
Explorando la palabra clave yield en Python
La palabra clave yield controla el flujo de una función generadora. En lugar de salir de la función como ocurre con return, yield devuelve un valor pero recuerda el estado de sus variables locales.
El generador devuelto por la llamada a yield se puede asignar a una variable e iterar con la función next() – esto ejecutará la función hasta el primer yield que encuentre. Cuando se alcanza yield, la ejecución de la función se suspende y se guarda su estado. Así, podemos reanudar su ejecución cuando queramos.
La función continuará desde la llamada a yield. Por ejemplo:
defyield_multiple_statements():yield"This is the first statement"yield"This is the second statement"yield"This is the third statement"yield"This is the last statement. Don't call next again!"
example = yield_multiple_statements()print(next(example))print(next(example))print(next(example))print(next(example))print(next(example))"""
This is the first statement
This is the second statement
This is the third statement
This is the last statement. Don't call next again or else!
--------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-25-4aaf9c871f91> in <module>()
11 print(next(example))
12 print(next(example))
---> 13 print(next(example))
StopIteration:
"""Con la tecnología de
En el ejemplo anterior, nuestro generador tiene cuatro llamadas a yield, pero intentamos llamar a next cinco veces, lo que provoca una excepción StopIteration. Ocurre porque el generador no es una serie infinita, así que llamarlo más veces de las previstas lo agota.
Resumen
En resumen, los iteradores son objetos que se pueden recorrer, y los generadores son funciones especiales que aprovechan la evaluación perezosa. Si implementas tu propio iterador, debes crear los métodos __iter__() y __next__(); en cambio, un generador se puede implementar usando la palabra clave yield en una función o en una comprensión de Python.
Puede que prefieras un iterador a medida frente a un generador cuando necesites un objeto con un comportamiento complejo de mantenimiento de estado o quieras exponer otros métodos más allá de __next__(), __iter__() y __init__(). Por otro lado, un generador puede ser preferible al trabajar con grandes volúmenes de datos, ya que no almacenan su contenido en memoria, o cuando no es necesario implementar un iterador completo.
¿Cuál es la diferencia entre un iterador y un generador en Python?⌄
Un iterador es cualquier objeto que implementa __iter__() y __next__(). Un generador es una forma más sencilla de crear un iterador usando una función con la palabra clave yield. Todos los generadores son iteradores, pero no todos los iteradores son generadores.
¿Cuándo debería usar un generador en lugar de una lista en Python?⌄
Usa un generador para secuencias grandes o infinitas, o cuando te importe la eficiencia de memoria. Las listas guardan todos los elementos a la vez en memoria, mientras que los generadores producen un valor cada vez. Para conjuntos pequeños que reutilizarás, una lista suele ser suficiente.
¿Qué hace la palabra clave yield en Python?⌄
La palabra clave yield convierte una función en un generador. En lugar de devolver y salir, yield pausa la función, devuelve un valor y recuerda su estado para reanudar la ejecución en la siguiente llamada.
¿Cómo se crea un generador en Python?⌄
O bien escribes una función que use yield en lugar de return, o utilizas una expresión generadora: la misma sintaxis que una comprensión de lista pero con paréntesis, por ejemplo (x * 2 for x in range(10)).
¿Son más rápidos los generadores que los iteradores en Python?
⌄
No en velocidad pura, pero sí son más eficientes en memoria porque generan los valores bajo demanda. Para datos grandes eso suele traducirse en mejor rendimiento global; para conjuntos pequeños, la diferencia es mínima.
Un tutorial introductorio completo sobre los bucles en Python. ¡Aprende y practica los bucles while y Bucle for, los bucles anidados, las palabras clave break y continue, la función range y mucho más!
Descubre cómo escribir funciones en Python reutilizables y eficientes. Domina los parámetros, las sentencias return y temas avanzados como las funciones lambda. Organiza mejor tu código con main() y otras buenas prácticas.
Aprende sobre los conjuntos en Python: qué son, cómo crearlos, cuándo usarlos, funciones incorporadas y su relación con las operaciones de la teoría de conjuntos.
En este tutorial, aprenderás todo sobre las cadenas de Python: trocearlas y encadenarlas, manipularlas y darles formato con la clase Formatter, cadenas f, plantillas y ¡mucho más!
Los iteradores son objetos sobre los que se puede iterar. Son una característica habitual del lenguaje Python, muy útil para bucles y comprensiones de listas. Cualquier objeto que pueda generar un iterador se denomina iterable.
Crear un iterador conlleva bastante trabajo. Por ejemplo, la implementación de cada objeto iterador debe incluir los métodos
__iter__()y__next__(). Además de este requisito, la implementación debe llevar un seguimiento del estado interno del objeto y lanzar una excepciónStopIterationcuando ya no queden valores que devolver. Estas reglas se conocen como el protocolo de iteradores.Implementar tu propio iterador es un proceso largo y no siempre necesario. Una alternativa más sencilla es usar un generador. Los generadores son un tipo especial de función que utiliza la palabra clave
yieldpara devolver un iterador que se puede recorrer, un valor cada vez.Saber identificar cuándo conviene implementar un iterador o usar un generador mejorará tus habilidades como programador de Python. En el resto de este tutorial, destacaremos las diferencias entre ambos para ayudarte a elegir la mejor opción en cada caso.
Glosario
Término
Definición
Iterable
Objeto de Python que se puede recorrer en un bucle. Ejemplos de iterables: listas, conjuntos, tuplas, diccionarios, cadenas, etc.
Iterador
Objeto sobre el que se puede iterar. Por tanto, los iteradores contienen un número contable de valores.
Generador
Tipo especial de función que no devuelve un único valor: devuelve un objeto iterador con una secuencia de valores.
Evaluación perezosa
Estrategia de evaluación por la que ciertos objetos solo se generan cuando se necesitan. Por ello, en algunos círculos de desarrollo también se conoce como «call-by-need».
Protocolo de iteradores
Conjunto de reglas que se deben seguir para definir un iterador en Python.
next()
Función integrada que devuelve el siguiente elemento de un iterador.
iter()
Función integrada que convierte un iterable en un iterador.
yield()
Palabra clave de Python similar a return, salvo que
yielddevuelve un objeto generador en lugar de un valor.Iteradores e iterables en Python
Los iterables son objetos capaces de devolver sus elementos uno a uno: se pueden recorrer. Las estructuras de datos integradas populares de Python, como listas, tuplas y conjuntos, son iterables. Otras estructuras como las cadenas y los diccionarios también lo son: una cadena puede iterarse carácter a carácter, y las claves de un diccionario se pueden recorrer. Como regla general, considera iterable a cualquier objeto que puedas usar en un bucle for.
Explorar iterables de Python con ejemplos
Dado lo anterior, podemos concluir que todos los iteradores también son iterables. Sin embargo, no todo iterable es necesariamente un iterador. Un iterable solo produce un iterador cuando se itera sobre él.
Para demostrarlo, vamos a instanciar una lista, que es un iterable, y a obtener un iterador llamando a la función integrada
iter()sobre la lista.Aunque la lista por sí sola no es un iterador, al llamar a
iter()se convierte en un iterador y se devuelve el objeto iterador.Para demostrar que no todos los iterables son iteradores, vamos a crear la misma lista e intentar llamar a la función
next(), que devuelve el siguiente elemento de un iterador.En el código anterior, puedes ver que intentar llamar a
next()sobre la lista lanza unTypeError– aprende más sobre excepciones y manejo de errores en Python. Esto ocurre porque una lista es un iterable, no un iterador.Explorar iteradores de Python con ejemplos
Por tanto, si el objetivo es iterar una lista, primero hay que obtener un objeto iterador. Solo entonces podremos avanzar por los valores de la lista.
Python crea automáticamente un objeto iterador cuando intentas recorrer un objeto iterable.
Cuando se captura la excepción
StopIteration, el bucle termina.Los valores de un iterador solo se pueden recuperar de izquierda a derecha. Python no tiene una función
previous()que permita retroceder en un iterador.La naturaleza perezosa de los iteradores
Es posible definir varios iteradores basados en el mismo objeto iterable. Cada iterador mantiene su propio estado de progreso. Así, si defines varias instancias de iterador de un mismo iterable, puedes llegar al final con una mientras otra sigue al principio.
Observa que
iterator_bimprime el primer elemento de la serie.Podemos decir que los iteradores tienen una naturaleza perezosa: cuando se crea un iterador, los elementos no se producen hasta que se solicitan. Es decir, los elementos de nuestra lista solo se devolverán cuando los pidamos explícitamente con
next(iter(list_instance)).No obstante, también puedes extraer todos los valores de un iterador de una vez llamando a un contenedor iterable integrado (p. ej.,
list(),set(),tuple()) sobre el objeto iterador para forzar que genere todos sus elementos a la vez.No es recomendable con iteradores grandes porque obliga a generar todos los elementos y mantenerlos en memoria a la vez, lo que va en contra de la evaluación perezosa.
Cuando un conjunto de datos es demasiado grande para caber cómodamente en memoria, o cuando quieres iteración perezosa sin escribir una clase de iterador completa, un generador suele encajar mejor.
Generadores en Python
La alternativa más ágil a implementar un iterador es usar un generador. Aunque los generadores se parecen a las funciones de Python normales, no son lo mismo. Para empezar, un generador no devuelve elementos como tal. En su lugar, utiliza la palabra clave
yieldpara generarlos sobre la marcha. Por eso decimos que un generador es una función especial que se apoya en la evaluación perezosa.Los generadores no almacenan su contenido en memoria como lo haría un iterable típico. Por ejemplo, si quisiéramos calcular todos los divisores de un entero positivo, normalmente escribiríamos una función tradicional (puedes aprender más sobre las funciones en Python en este tutorial) así:
El código anterior devuelve la lista completa de divisores. Sin embargo, observa la diferencia cuando usamos un generador en lugar de una función tradicional de Python:
Como hemos usado
yielden lugar dereturn, la función no termina tras ejecutarse. En esencia, le hemos dicho a Python que cree un objeto generador en lugar de una función tradicional, lo que permite mantener el estado del generador.En consecuencia, es posible llamar a
next()sobre el iterador perezoso para obtener los elementos de la serie uno a uno.Otra forma de crear un generador es con una comprensión de generador. Las expresiones generadoras usan una sintaxis muy similar a la de las comprensiones de listas, pero con paréntesis en lugar de corchetes.
Explorando la palabra clave yield en Python
La palabra clave
yieldcontrola el flujo de una función generadora. En lugar de salir de la función como ocurre conreturn,yielddevuelve un valor pero recuerda el estado de sus variables locales.El generador devuelto por la llamada a
yieldse puede asignar a una variable e iterar con la funciónnext()– esto ejecutará la función hasta el primeryieldque encuentre. Cuando se alcanzayield, la ejecución de la función se suspende y se guarda su estado. Así, podemos reanudar su ejecución cuando queramos.La función continuará desde la llamada a
yield. Por ejemplo:En el ejemplo anterior, nuestro generador tiene cuatro llamadas a
yield, pero intentamos llamar anextcinco veces, lo que provoca una excepciónStopIteration. Ocurre porque el generador no es una serie infinita, así que llamarlo más veces de las previstas lo agota.Resumen
En resumen, los iteradores son objetos que se pueden recorrer, y los generadores son funciones especiales que aprovechan la evaluación perezosa. Si implementas tu propio iterador, debes crear los métodos
__iter__()y__next__(); en cambio, un generador se puede implementar usando la palabra claveyielden una función o en una comprensión de Python.Puede que prefieras un iterador a medida frente a un generador cuando necesites un objeto con un comportamiento complejo de mantenimiento de estado o quieras exponer otros métodos más allá de
__next__(),__iter__()y__init__(). Por otro lado, un generador puede ser preferible al trabajar con grandes volúmenes de datos, ya que no almacenan su contenido en memoria, o cuando no es necesario implementar un iterador completo.