Ciclos em Python, Generators - parte 2
0
comentários
Sérgio Silva
2013-04-29 21:28 (GMT)
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.
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.
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:
A inspiração para este post veio daqui.
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:
- Ciclos em Python, o básico
- Ciclos em Python e os Iteráveis
- Ciclos em Python, mais exemplos de Iteráveis
- Ciclos em Python, uso de Iteráveis fora dos ciclos
- Ciclos em Python, problemas comuns e os índices
- Ciclos em Python, iterar sobre duas listas
- Ciclos em Python, iteração personalizada
- Ciclos em Python, Generators - parte 1
- Ciclos em Python, Generators - parte 2 (post actual)
- Ciclos em Python, Generators - parte 3
- Ciclos em Python, operações de baixo nível
- Ciclos em Python, como tornar os nossos objectos em Iteráveis
- Ciclos em Python, conclusão
A inspiração para este post veio daqui.
0
comentários