Os generators são uma poderosa ferramenta, providenciada pela linguagem de programação Python, para a abstracção da iteração. Tal como as funções são boas para abstrair uma série de expressões, e as classes são boas para abstrair uma colecção de dados e métodos para os manipular, os generators são bons para abstrair a iteração.

Como exemplo, vamos considerar um programa que lê um ficheiro de texto e faz algum processamento com as suas linhas. O ficheiro poderá ter comentários, que são linhas que começam por "#" e pode ter linhas em branco, ambas devem ser ignoradas pelo programa.

f = open("meu_ficheiro.txt")
for linha in f:
    linha = linha.strip()
    if linha.startswith("#"):
        #linha de comentário, não faz nada
        continue
    if not linha:
        #linha em branco, não faz nada
        continue

    #linha com interesse
    faz_algo(linha)

Aqui temos esboço do programa. Abrimos o ficheiro, percorremos as suas linhas. Removemos os espaços em branco do início e do fim das linhas, depois verificamos se a linha começa com "#", se sim não consideramos a linha e avançamos para a próxima, da mesma forma se a linha for uma linha em branco, avançamos também para a próxima linha do ficheiro.

Finalmente, se a linha não é um comentário nem uma linha em branco, então tratamos a linha da forma que entendermos. No fim a função faz_algo() terá processado todas as linhas do ficheiro que não sejam comentários nem linhas em branco.

Este programa funciona bem, mas mistura dois conceitos. O primeiro é, o que são linhas interessantes e quais devem ser ignoradas? O segundo é, o que fazer com as linhas interessantes? Estas duas questões poderão não ter nada a haver uma com a outra. Poderemos, por exemplo, precisar de reutilizar o primeiro (conceito) para processar diferentes tipos de ficheiros.


Deve-se evitar misturar conceitos, porque:
- Torna o código menos legível;
- É mais difícil reutilizar o código;
- Dificulta a manutenção futura do código;
- É mais propenso a bugs.


def linhas_interessantes(f)
    for linha in f:
        linha = linha.strip()
        if linha.startswith("#"):
            #linha de comentário, não faz nada
            continue
        if not linha:
            #linha em branco, não faz nada
            continue

        #linha com interesse
        yield linha 

with open("meu_ficheiro.txt") as f:
    for linha in linhas_interessantes(f):
        faz_algo(linha)


with open("meu_outro_ficheiro.txt") as f:
    for linha in linhas_interessantes(f):
        faz_outra_coisa(linha)
    

Utilizando um generator podemos dividir o ciclo em duas partes. A primeira parte, responsável por identificar quais as linhas que são interessantes poderá ser implementada por um generator. O método linhas_interessantes() do exemplo acima é esse generator, ele analisa cada linha do ficheiro, identifica as linhas interessantes e em vez de fazer o processamento dessas linhas faz o yield das mesmas.

Agora podemos usar o generator linhas_interessantes() num ciclo for e obter apenas as linhas interessantes, todas as linhas que não interessam são filtradas pelo generator. Assim torna-se mais simples reutilizar a lógica de obter as linhas interessantes noutros ciclos.


De reparar que a forma de pensarmos a lógica da iteração também foi alterada. Passamos de inicialmente pensarmos a lógica como, "percorrer as linhas de um ficheiro e manualmente escolher as linhas interessantes", para, com a ajuda do generator, podermos passar a pensar como, "percorrer directamente as linhas interessantes de um ficheiro". Isto permite-nos ter um nível de abstracção mais elevado.



No próximo post continuarei a falar sobre ciclos em Python e também sobre generators.


Este post faz parte da série de posts sobre Ciclos e Interáveis em Python:
  1. Ciclos em Python, o básico
  2. Ciclos em Python e os Iteráveis
  3. Ciclos em Python, mais exemplos de Iteráveis
  4. Ciclos em Python, uso de Iteráveis fora dos ciclos
  5. Ciclos em Python, problemas comuns e os índices
  6. Ciclos em Python, iterar sobre duas listas
  7. Ciclos em Python, iteração personalizada
  8. Ciclos em Python, Generators - parte 1
  9. Ciclos em Python, Generators - parte 2 (post actual)
  10. Ciclos em Python, Generators - parte 3
  11. Ciclos em Python, operações de baixo nível
  12. Ciclos em Python, como tornar os nossos objectos em Iteráveis
  13. Ciclos em Python, conclusão


A inspiração para este post veio daqui.