Generadores en Python

Generadores en Python

Este artículo explica los generadores en Python.

YouTube Video

Generadores en Python

Visión general

Los generadores en Python son un tipo de iterador y una característica poderosa para realizar procesos repetitivos de manera eficiente. Permiten escribir código eficiente en memoria al trabajar con grandes cantidades de datos.

¿Qué es un generador?

Un generador en Python es una función especial que produce un valor a la vez, definida usando la palabra clave yield. Su característica es que pausa la ejecución manteniendo su estado y puede reanudarse más tarde.

Conceptos básicos de yield

yield es una palabra clave que devuelve un valor y pausa la ejecución de la función al mismo tiempo.

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • Cuando se llama, esta función devuelve un objeto generador que produce valores uno por uno.
  • Si llamas a next() cuando no hay un valor siguiente, ocurrirá un error StopIteration.

next() y StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • Al manejar explícitamente el error StopIteration de esta manera, puedes detectar cuándo un generador ha finalizado.

send(value)

Llamar a send(value) reanuda el generador y envía value a la posición de la expresión yield. El valor enviado puede ser recibido en el generador como el valor de retorno de la expresión yield. En la primera llamada, no puedes enviar nada que no sea None con send(value), así que debes usar next() o send(None).

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • Con send(10), el yield del generador se convierte en una expresión que devuelve 10, y 10 se asigna a x.

throw()

Llamar a throw reanuda el generador y lanza una excepción en la posición del yield pausado. Puedes manejar la excepción dentro del generador para continuar el procesamiento. Si la excepción no es capturada, se propaga hacia afuera y el generador termina.

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • En este código, se llama a throw para inyectar una excepción en el generador. En el lado del generador, la excepción es manejada y se devuelve recovered.

close()

Llamar a close() termina el generador. Dentro del generador, puedes realizar tareas de limpieza usando finally. Llamar a next() o send() después de llamar a close() provoca un error StopIteration.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Este código muestra que al llamar a close() se termina el generador y se ejecuta el proceso de limpieza en finally.

yield from

yield from es una sintaxis usada para delegar a un subgenerador. Es una manera sencilla de llamar a otro generador dentro de un generador y pasar todos sus valores al ámbito externo.

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • Este código delega todos los valores del subgenerador al generador externo usando yield from, y luego produce 3.

Relación con los iteradores

Los generadores implementan internamente __iter__() y __next__(), lo que los convierte en un tipo de iterador. Por lo tanto, son totalmente compatibles con operaciones iterables como los bucles for.

Integración con bucles for

En Python, un bucle for utiliza internamente next() para recuperar los valores automáticamente.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Con este método, el manejo de StopIteration también es automático.

Creando generadores infinitos

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

Es posible crear bucles infinitos, pero se debe tener cuidado al usarlos.

Expresiones generadoras

Las expresiones generadoras, escritas usando paréntesis, permiten definir generadores con una sintaxis similar a las comprensiones de listas.

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

A diferencia de las comprensiones de listas, no cargan todos los elementos en memoria a la vez, lo que las hace más eficientes en memoria.

Manejo de errores en generadores

Pueden ocurrir excepciones dentro de un generador. En estos casos, se utiliza try-except como en cualquier código habitual de Python.

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

En este ejemplo, se realiza un manejo adecuado de errores en caso de una división por cero.

Traza de pila de un generador

Si se produce una excepción dentro del generador, será lanzada cuando se reanude el generador.

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • Este generador devuelve primero 1. El error que se produce al reanudar se captura y se muestra como un mensaje de error.

Ejemplos de uso de generadores

Leer un archivo línea por línea (adecuado para archivos grandes)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Esta función lee un archivo de texto línea por línea usando un iterador, elimina los espacios en blanco de cada línea y lo devuelve como un generador, lo que permite procesar archivos grandes con bajo uso de memoria.

Generador para la secuencia de Fibonacci

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • Este código utiliza un generador para generar secuencialmente números de Fibonacci menores que el límite superior y los muestra utilizando un bucle for.

Casos de uso

Los generadores también se pueden usar en los siguientes escenarios.

  • Procesamiento secuencial de archivos CSV o de registro grandes
  • Paginación de API
  • Procesamiento de datos en streaming (por ejemplo, Kafka, dispositivos IoT)

Resumen

Concepto Punto clave
yield Pausa y devuelve un valor
Función generadora Una función que contiene yield y devuelve un iterador cuando se llama
Ventajas Eficiente en memoria e ideal para procesar grandes conjuntos de datos
Expresión generadora Permite una sintaxis concisa como (x for x in iterable)

Al usar generadores, puedes procesar grandes volúmenes de datos de manera eficiente, ahorrando memoria y manteniendo un código conciso.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video