Contents

Trabalhar com geradores em Python

Se estiver a ler linhas de um ficheiro de registo ou a processar uma longa lista de itens, uma opção é carregar todos os dados para a memória. No entanto, essa abordagem pode usar muita memória e prejudicar o desempenho. Os geradores oferecem uma solução valiosa.

Os geradores aliviam a necessidade de carregar grandes quantidades de dados na memória de uma só vez. São particularmente benéficos em situações que envolvam conjuntos de dados substanciais, séries infinitas ou qualquer circunstância que coloque um prémio na gestão da memória.

O que são geradores?

/pt/images/wind-energy-generator.jpg

Um gerador pode ser definido como um tipo particular de função que permite o processamento iterativo de uma série de elementos. Ao contrário das funções tradicionais que fornecem um conjunto de dados completo de uma só vez, os geradores produzem progressivamente componentes individuais a pedido. Por conseguinte, são muito eficazes quando se trata de colecções de informação extensas ou infinitas.

Uma função Python padrão é normalmente concebida para calcular um único valor e devolvê-lo, enquanto uma função geradora opera numa base iterativa. Em vez de calcular um valor e devolvê-lo de uma só vez, uma função geradora produz vários valores ao longo do tempo através de uma série de pausas e retomadas da sua execução.

A geração de funcionalidades em linguagens de programação envolve frequentemente a determinação da forma como os dados serão produzidos ou executados. Existe uma diferença fundamental entre as funções padrão e as funções geradoras no que diz respeito à sua abordagem para fornecer resultados. As funções normais utilizam normalmente a palavra-chave “return” como meio de saída de dados, enquanto as funções geradoras utilizam a instrução “yield” para este fim.

Como criar um gerador

Para construir uma função geradora, em vez de empregar uma instrução return tradicional, utilize uma instrução yield situada no corpo da função. A palavra-chave yield serve não apenas como uma instrução para a função produzir um resultado, mas também permite que ela preserve seu status atual, facilitando assim possíveis retomadas.

Certamente, aqui está um exemplo de uma função geradora básica em Python:

 def numeric_generator():
  yield 1
  yield 2
  yield 3

gen = numeric_generator()

Esta função em particular, quando executada, produz uma sequência de números que vão de 1 a 3.

A instrução yield serve um propósito único na programação funcional, permitindo a suspensão e o reinício da execução da função enquanto preserva o seu estado atual, incluindo quaisquer variáveis definidas localmente, para invocações subsequentes. Isto permite a continuação contínua de computações sem a necessidade de reinícios explícitos ou de contabilidade adicional.

Quando se armazena uma função geradora dentro de uma variável, resulta na criação de um objeto gerador que pode ser utilizado para várias operações.

/pt/images/generator-object.jpg

Trabalhando com geradores

Os geradores oferecem uma gama versátil de aplicações em vários contextos, incluindo a utilização em loops for e compreensões de lista, bem como em estruturas iterativas mais extensas. Além disso, os geradores são adequados para serem utilizados como parâmetros de entrada para inúmeras funções.

Depois de construir um gerador, pode utilizar uma construção de ciclo conhecida como “ciclo for” para percorrer o seu resultado iterativamente. Isto permite-lhe processar sistematicamente cada elemento da sequência gerada pela função sem ter de aceder manualmente e manipulá-los individualmente.

 for i in numeric_generator():
  print(i)

Também se pode utilizar a função subsequente para obter valores sequencialmente:

 print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3

Esta abordagem permite-lhe ter uma maior influência sobre a entidade geradora.

/pt/images/numeric-generator-in-action.jpg

Os geradores têm a capacidade de manter o seu estado interno. Cada instância da palavra-chave yield dentro de uma função serve como uma oportunidade para o gerador fazer uma pausa no seu progresso e registar a sua posição atual. Ao invocar o método next() no objeto gerador, o controlo é transferido de volta para a instrução yield anterior, retomando efetivamente a execução nesse ponto específico.

Também é possível transmitir dados a um gerador utilizando o método send() , que permite o fornecimento de valores.

 def generator_with_send():
    # First yield: Receive a value
    x = yield
    print(f"Received: {x}")

    # Second yield: Receive another value
    y = yield
    print(f"Received: {y}")

    # Third yield: Yield the sum
    yield x \\+ y

gen = generator_with_send()

# Start generator and reach first yield
next(gen)

# Send 10 into generator, received at first yield
result = gen.send(10)

# Send 5 into generator, received at second yield
result = gen.send(5)

# Print result of third yield
print(result) 

O método send() nos geradores Python fornece um mecanismo para obter valores de saída e controlar o fluxo de execução enviando valores de volta para a função geradora. Esta técnica pode ser útil em situações em que é necessário fazer uma pausa na execução de um gerador ou escrever programas cooperativos mais complexos que envolvam várias chamadas a funções geradoras.

/pt/images/sending-values-to-generators.jpg

Usando expressões de gerador

As expressões de gerador oferecem uma maneira eficiente de criar um gerador direto e sem nome por meio de uma sintaxe abreviada que utiliza parênteses em vez de colchetes. Estas assemelham-se a compreensões de lista em muitos aspectos, mas exibem características distintas que as distinguem da sua construção homóloga.

Aqui está um exemplo:

 gen = (i**2 for i in range(10))

for x in gen:
  print(x)

O código constrói um objeto gerador que produz os quadrados de números inteiros que vão de 0 a um limite superior especificado, utilizando uma expressão geradora. Esta abordagem é particularmente adequada para situações em que apenas uma parte do resultado precisa de ser gerada num dado momento, uma vez que permite uma produção eficiente e flexível de valores a pedido.

/pt/images/generator-expressions.jpg

Usando geradores para processamento de dados

Os geradores Python oferecem uma solução elegante para descrever fluxos de dados enquanto minimizam o uso de memória. Ao dominarem a sua utilização, os programadores podem efetivamente lidar com tarefas complexas de processamento de dados com facilidade.

Ao trabalhar com conjuntos de dados extensos, é prudente considerar a utilização de geradores pela sua capacidade de lidar com tarefas computacionais árduas, mantendo um ambiente de codificação ágil e simplificado.