Como Verificar se uma Função é um Gerador em Python

PythonBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá como identificar funções geradoras em Python e entender as diferenças fundamentais entre funções regulares e geradores. Este conhecimento é crucial para escrever código eficiente e que economiza memória, especialmente ao lidar com grandes conjuntos de dados ou sequências infinitas.

O laboratório irá guiá-lo através da diferenciação entre funções e geradores, verificando seus tipos usando inspect.isgeneratorfunction e testando a presença da palavra-chave yield. Você criará um script Python para ilustrar as diferenças, observando como as funções retornam um resultado completo, enquanto os geradores produzem valores iterativamente, pausando e retomando a execução conforme necessário.

Diferenciar Funções e Geradores

Nesta etapa, você aprenderá as principais diferenças entre funções Python regulares e geradores. Compreender essa distinção é crucial para escrever código eficiente e que economiza memória, especialmente ao lidar com grandes conjuntos de dados ou sequências infinitas.

Funções:

Uma função é um bloco de código que executa uma tarefa específica. Quando uma função é chamada, ela executa seu código, potencialmente realiza cálculos e retorna um valor (ou None se nenhuma instrução return explícita estiver presente). O estado da função não é preservado entre as chamadas.

Geradores:

Um gerador é um tipo especial de função que usa a palavra-chave yield em vez de return. Quando um gerador é chamado, ele retorna um objeto iterador. Cada vez que você solicita um valor do iterador, o gerador executa até encontrar uma instrução yield. O gerador então pausa, salva seu estado e produz o valor. Na próxima vez que um valor é solicitado, o gerador retoma de onde parou.

Vamos ilustrar isso com um exemplo. Primeiro, crie um arquivo chamado function_vs_generator.py em seu diretório ~/project usando o 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

Agora, execute o script Python:

python ~/project/function_vs_generator.py

Você deve ver a seguinte saída:

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

Tanto a função quanto o gerador produzem o mesmo resultado. No entanto, a principal diferença reside em como eles alcançam isso. A função calcula todos os quadrados e os armazena em uma lista antes de retornar. O gerador, por outro lado, produz cada quadrado um de cada vez, somente quando solicitado.

Para ilustrar ainda mais a diferença, vamos modificar o script para imprimir o tipo do objeto retornado:

## ~/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))

Execute o script novamente:

python ~/project/function_vs_generator.py

A saída será:

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

Isso mostra claramente que a função retorna uma list, enquanto o gerador retorna um objeto generator. Os geradores são eficientes em termos de memória porque não armazenam todos os valores na memória de uma vez. Eles geram valores sob demanda.

Verificar o Tipo com inspect.isgeneratorfunction

Na etapa anterior, você aprendeu a diferença básica entre funções e geradores. Agora, vamos explorar como determinar programaticamente se uma função é uma função geradora usando o método inspect.isgeneratorfunction(). Isso é particularmente útil quando você está trabalhando com código onde pode não conhecer os detalhes de implementação de uma função.

O módulo inspect em Python fornece ferramentas para introspecção, o que significa examinar as características internas de objetos como funções, classes, módulos, etc. O método inspect.isgeneratorfunction() verifica especificamente se um determinado objeto é uma função geradora.

Vamos modificar o arquivo function_vs_generator.py em seu diretório ~/project para incluir essa verificação. Abra o arquivo no VS Code e adicione o seguinte 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))

Neste código, primeiro importamos o módulo inspect. Em seguida, usamos inspect.isgeneratorfunction() para verificar tanto square_numbers_function quanto square_numbers_generator.

Agora, execute o script Python:

python ~/project/function_vs_generator.py

Você deve ver a seguinte saída:

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

Isso confirma que inspect.isgeneratorfunction() identifica corretamente a função geradora.

Este método é muito útil quando você precisa lidar com funções de maneira diferente, dependendo se elas são geradores ou funções regulares. Por exemplo, você pode querer iterar sobre os resultados de um gerador, mas simplesmente chamar uma função regular.

Testar o Uso da Palavra-chave yield

Nesta etapa, vamos nos concentrar em como a presença da palavra-chave yield define fundamentalmente uma função geradora. Uma função é considerada um gerador se e somente se contiver pelo menos uma instrução yield. Vamos criar um teste simples para confirmar isso.

Abra o arquivo function_vs_generator.py em seu diretório ~/project usando o VS Code. Adicionaremos uma função que não usa yield e veremos como ela se comporta quando tratada como um potencial gerador.

## ~/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))

Neste código atualizado, adicionamos uma função return_list que simplesmente retorna uma lista usando uma compreensão de lista. Ela não contém a palavra-chave yield. Em seguida, usamos inspect.isgeneratorfunction() para verificar se return_list é uma função geradora.

Agora, execute o script Python:

python ~/project/function_vs_generator.py

Você deve ver a seguinte saída:

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

Como esperado, return_list não é identificada como uma função geradora porque não usa a palavra-chave yield. Isso demonstra que a palavra-chave yield é essencial para definir um gerador em Python. Sem ela, a função se comporta como uma função regular, retornando um valor (ou None) e não preservando seu estado entre as chamadas.

Resumo

Neste laboratório, você aprendeu as diferenças fundamentais entre funções Python regulares e geradores. Funções executam um bloco de código e retornam um valor, enquanto geradores, usando a palavra-chave yield, retornam um objeto iterador que produz valores sob demanda, pausando e salvando seu estado entre as chamadas.

Você explorou como os geradores são eficientes em termos de memória, especialmente ao lidar com grandes conjuntos de dados, pois eles geram valores um de cada vez, em vez de armazená-los todos na memória como as funções. O laboratório demonstrou essa distinção por meio de um exemplo prático de elevação de números ao quadrado usando tanto uma função quanto um gerador.