Cómo comprobar si una función es un generador en Python

PythonPythonBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás cómo identificar funciones generadoras en Python y comprender las diferencias fundamentales entre funciones regulares y generadores. Este conocimiento es crucial para escribir código eficiente y amigable con la memoria, especialmente cuando se trabaja con conjuntos de datos grandes o secuencias infinitas.

El laboratorio te guiará a través de la diferenciación entre funciones y generadores, verificando sus tipos utilizando inspect.isgeneratorfunction y comprobando la presencia de la palabra clave yield. Crearás un script de Python para ilustrar las diferencias, observando cómo las funciones devuelven un resultado completo mientras que los generadores producen valores de forma iterativa, pausando y reanudando la ejecución según sea necesario.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ModulesandPackagesGroup -.-> python/standard_libraries("Common Standard Libraries") python/AdvancedTopicsGroup -.-> python/generators("Generators") subgraph Lab Skills python/function_definition -.-> lab-559519{{"Cómo comprobar si una función es un generador en Python"}} python/standard_libraries -.-> lab-559519{{"Cómo comprobar si una función es un generador en Python"}} python/generators -.-> lab-559519{{"Cómo comprobar si una función es un generador en Python"}} end

Diferenciar Funciones y Generadores

En este paso, aprenderás las diferencias clave entre las funciones regulares de Python y los generadores. Comprender esta distinción es crucial para escribir código eficiente y amigable con la memoria, especialmente cuando se trabaja con conjuntos de datos grandes o secuencias infinitas.

Funciones:

Una función es un bloque de código que realiza una tarea específica. Cuando se llama a una función, ejecuta su código, potencialmente realiza cálculos y devuelve un valor (o None si no hay una declaración return explícita). El estado de la función no se conserva entre llamadas.

Generadores:

Un generador es un tipo especial de función que utiliza la palabra clave yield en lugar de return. Cuando se llama a un generador, devuelve un objeto iterador. Cada vez que solicitas un valor del iterador, el generador se ejecuta hasta que encuentra una declaración yield. El generador luego se pausa, guarda su estado y produce el valor. La próxima vez que se solicite un valor, el generador reanuda desde donde se detuvo.

Veamos un ejemplo para ilustrar esto. Primero, crea un archivo llamado function_vs_generator.py en tu directorio ~/project utilizando el editor VS Code.

## ~/project/function_vs_generator.py

## Regular function
def square_numbers_function(numbers):
    result = []
    for number in numbers:
        result.append(number * number)
    return result

## Generator function
def square_numbers_generator(numbers):
    for number in numbers:
        yield number * number

## Example usage
numbers = [1, 2, 3, 4, 5]

## Using the function
function_result = square_numbers_function(numbers)
print("Function Result:", function_result)

## Using the generator
generator_result = square_numbers_generator(numbers)
print("Generator Result:", list(generator_result)) ## Convert generator to list for printing

Ahora, ejecuta el script de Python:

python ~/project/function_vs_generator.py

Deberías ver la siguiente salida:

Function Result: [1, 4, 9, 16, 25]
Generator Result: [1, 4, 9, 16, 25]

Tanto la función como el generador producen el mismo resultado. Sin embargo, la diferencia clave radica en cómo lo logran. La función calcula todos los cuadrados y los almacena en una lista antes de devolverlos. El generador, por otro lado, produce cada cuadrado uno a la vez, solo cuando se solicita.

Para ilustrar aún más la diferencia, modifiquemos el script para imprimir el tipo del objeto devuelto:

## ~/project/function_vs_generator.py

## Regular function
def square_numbers_function(numbers):
    result = []
    for number in numbers:
        result.append(number * number)
    return result

## Generator function
def square_numbers_generator(numbers):
    for number in numbers:
        yield number * number

## Example usage
numbers = [1, 2, 3, 4, 5]

## Using the function
function_result = square_numbers_function(numbers)
print("Function Result Type:", type(function_result))

## Using the generator
generator_result = square_numbers_generator(numbers)
print("Generator Result Type:", type(generator_result))

Ejecuta el script nuevamente:

python ~/project/function_vs_generator.py

La salida será:

Function Result Type: <class 'list'>
Generator Result Type: <class 'generator'>

Esto muestra claramente que la función devuelve una list, mientras que el generador devuelve un objeto generator. Los generadores son eficientes en memoria porque no almacenan todos los valores en memoria al mismo tiempo. Generan valores a demanda.

Verificar el Tipo con inspect.isgeneratorfunction

En el paso anterior, aprendiste la diferencia básica entre funciones y generadores. Ahora, exploremos cómo determinar mediante programación si una función es una función generadora utilizando el método inspect.isgeneratorfunction(). Esto es especialmente útil cuando estás trabajando con código en el que es posible que no conozcas los detalles de implementación de una función.

El módulo inspect en Python proporciona herramientas para la introspección, lo que significa examinar las características internas de objetos como funciones, clases, módulos, etc. El método inspect.isgeneratorfunction() comprueba específicamente si un objeto dado es una función generadora.

Modifiquemos el archivo function_vs_generator.py en tu directorio ~/project para incluir esta comprobación. Abre el archivo en VS Code y agrega el siguiente código:

## ~/project/function_vs_generator.py

import inspect

## Regular function
def square_numbers_function(numbers):
    result = []
    for number in numbers:
        result.append(number * number)
    return result

## Generator function
def square_numbers_generator(numbers):
    for number in numbers:
        yield number * number

## Check if it's a generator function
print("Is square_numbers_function a generator function?", inspect.isgeneratorfunction(square_numbers_function))
print("Is square_numbers_generator a generator function?", inspect.isgeneratorfunction(square_numbers_generator))

En este código, primero importamos el módulo inspect. Luego, usamos inspect.isgeneratorfunction() para comprobar tanto la square_numbers_function como la square_numbers_generator.

Ahora, ejecuta el script de Python:

python ~/project/function_vs_generator.py

Deberías ver la siguiente salida:

Is square_numbers_function a generator function? False
Is square_numbers_generator a generator function? True

Esto confirma que inspect.isgeneratorfunction() identifica correctamente la función generadora.

Este método es muy útil cuando necesitas manejar funciones de manera diferente según si son generadores o funciones regulares. Por ejemplo, es posible que desees iterar sobre los resultados de un generador, pero simplemente llamar a una función regular.

Probar el Uso de la Palabra Clave yield

En este paso, nos centraremos en cómo la presencia de la palabra clave yield define fundamentalmente una función generadora. Una función se considera un generador si y solo si contiene al menos una declaración yield. Creemos una prueba simple para confirmar esto.

Abre el archivo function_vs_generator.py en tu directorio ~/project utilizando VS Code. Agregaremos una función que no use yield y veremos cómo se comporta cuando se la trata como un posible generador.

## ~/project/function_vs_generator.py

import inspect

## Regular function
def square_numbers_function(numbers):
    result = []
    for number in numbers:
        result.append(number * number)
    return result

## Generator function
def square_numbers_generator(numbers):
    for number in numbers:
        yield number * number

## Function that returns a list, not a generator
def return_list(numbers):
    return [x for x in numbers]

## Check if it's a generator function
print("Is square_numbers_function a generator function?", inspect.isgeneratorfunction(square_numbers_function))
print("Is square_numbers_generator a generator function?", inspect.isgeneratorfunction(square_numbers_generator))
print("Is return_list a generator function?", inspect.isgeneratorfunction(return_list))

En este código actualizado, hemos agregado una función return_list que simplemente devuelve una lista utilizando una comprensión de lista. No contiene la palabra clave yield. Luego, usamos inspect.isgeneratorfunction() para comprobar si return_list es una función generadora.

Ahora, ejecuta el script de Python:

python ~/project/function_vs_generator.py

Deberías ver la siguiente salida:

Is square_numbers_function a generator function? False
Is square_numbers_generator a generator function? True
Is return_list a generator function? False

Como se esperaba, return_list no se identifica como una función generadora porque no usa la palabra clave yield. Esto demuestra que la palabra clave yield es esencial para definir un generador en Python. Sin ella, la función se comporta como una función regular, devolviendo un valor (o None) y no conservando su estado entre llamadas.

Resumen

En este laboratorio, aprendiste las diferencias fundamentales entre las funciones regulares de Python y los generadores. Las funciones ejecutan un bloque de código y devuelven un valor, mientras que los generadores, utilizando la palabra clave yield, devuelven un objeto iterador que produce valores a demanda, pausando y guardando su estado entre llamadas.

Exploraste cómo los generadores son eficientes en términos de memoria, especialmente cuando se trata de conjuntos de datos grandes, ya que generan valores uno a la vez en lugar de almacenarlos todos en memoria como lo hacen las funciones. El laboratorio demostró esta distinción a través de un ejemplo práctico de calcular el cuadrado de números utilizando tanto una función como un generador.