Ao escrever código, desenvolvedores iniciantes em Python raramente consideram a eficiência e a concisão, resultando em código prolixo e com baixo desempenho. Este artigo apresenta alguns exemplos práticos para inspirar iniciantes a escrever código Python de forma mais elegante.
Erro 1: Não utilizar estruturas de dados avançadas
1. Conjuntos (sets)
Muitos iniciantes subestimam o poder dos conjuntos (sets) e tuplas (tuples). Por exemplo, para obter a interseção de duas listas:
def elementos_comuns(lista1, lista2):
comuns = []
for item1 in lista1:
if item1 in lista2:
comuns.append(item1)
return comuns
Uma abordagem mais eficiente seria:
def elementos_comuns(lista1, lista2):
comuns = set(lista1).intersection(set(lista2))
return list(comuns)
2. Dicionários (dict)
Iniciantes costumam percorrer chaves e valores de um dicionário da seguinte forma:
meu_dict = {'a':1, 'b':2}
for chave in meu_dict:
print(chave, meu_dict[chave])
Uma maneira mais elegante de realizar esta operação:
meu_dict = {'a':1, 'b':2}
for chave, valor in meu_dict.items():
print(chave, valor)
Para a maioria dos projetos, esta abordagem é mais eficiente.
3. Tuplas (tuple)
Tuplas são imutáveis após sua criação, mas possuem diversas aplicações úteis. Muitas funções retornam tuplas, como enumerate() e dict.items(), e podem ser usadas em funções para retornar múltiplos valores. Além disso, permitem a extração conveniente de informações:
a, b = ('gato', 'cachorro')
No exemplo acima, os dois elementos da tupla são atribuídos a a e b. O mesmo princípio se aplica a mais valores:
a, b, c = ('gato', 'cachorro', 'tigre')
print(a, b, c)
Para extrair o primeiro e último elementos:
primeiro, *_, final = (1, 2, 3, 4, 5, 6)
print(primeiro, final)
# Saída: 1, 6
Para extrair primeiro, meio e último:
primeiro, *meio, final = (1, 2, 3, 4, 5, 6)
print(primeiro, meio, final)
# Saída: 1, [2, 3, 4, 5], 6
Tuplas também permitem a troca de variáveis:
(a, b, c) = (c, a, b)
Além disso, tuplas podem ser usadas como chaves de dicionário, o que é útil para armazenar dados como coordenadas geográficas (latitude e longitude).
Erro 2: Não utilizar gerenciadores de contexto
É comum ver iniicantes realizando operações de leitura de arquivos desta forma:
if os.path.exists(caminho_arquivo_dados):
arquivo_dados = open(caminho_arquivo_dados, 'r')
else:
raise OSERROR
print(arquivo_dados.read())
arquivo_dados.close()
Esta abordagem apresenta problemas significativos:
- O arquivo pode existir, mas estar bloqueado para leitura
- A leitura pode ser bem-sucedida, mas ocorrer erros durante o processamento do objeto do arquivo
- É possível esquecer de fechar o arquivo
A instrução with resolve esses problemas de forma elegante:
with open(caminho_arquivo_dados, 'r') as arquivo_dados:
print(arquivo_dados.read())
Esta abordagem captura exceções durante a abertura ou processamento do arquivo e garante seu fechamento automático após a conclusão das operações.
Erro 3: Não utilizar a biblioteca padrão
Módulos como itertools e collections são subutilizados por iniciantes.
itertools
Considere a seguinte tarefa com loops aninhados:
lista1 = range(1, 10)
lista2 = range(10, 20)
for item1 in lista1:
for item2 in lista2:
print(item1 * item2)
Esta operação pode ser simplificada usando product():
from itertools import product
lista1 = range(1, 10)
lista2 = range(10, 20)
for item1, item2 in product(lista1, lista2):
print(item1 * item2)
Ambos os códigos produzem o mesmo resultado, mas o segundo é mais conciso e eficiente. A biblioteca itertools oferece diversas funções úteis para iteração, incluindo:
count()- cria um iterador infinitocycle()- repete indefinidamente uma sequênciachain()- encadeia múltiplos iteráveisgroup()- agrupa elementos adjacentes iguais
collections
Um cenário comum é a construção de dicionários a partir de dados agrupados:
lista_consolidada = [('a', 1), ('b', 2), ('c', 3), ('b', 4)]
itens_por_id = {}
for id_, item in lista_consolidada:
if id_ not in itens_por_id:
itens_por_id[id_] = []
itens_por_id[id_].append(item)
Este padrão pode ser simplificado usando defaultdict():
from collections import defaultdict
itens_por_id = defaultdict(list)
lista_consolidada = [('a', 1), ('b', 2), ('c', 3), ('b', 4)]
for id_, item in lista_consolidada:
itens_por_id[id_].append(item)
Neste exemplo, defaultdict(list) retorna uma lista vazia automaticamente quando uma chave não existe.
Outro caso comum é a contagem de frequências:
cores = ['vermelho', 'azul', 'vermelho', 'verde', 'azul', 'azul']
resultado = {}
for cor in cores:
if resultado.get(cor) is None:
resultado[cor] = 1
else:
resultado[cor] += 1
print(resultado)
Isso pode ser simplificado com defaultdict():
cores = ['vermelho', 'azul', 'vermelho', 'verde', 'azul', 'azul']
contagem = defaultdict(int)
for cor in cores:
contagem[cor] += 1
print(contagem)
Ou de forma ainda mais concisa com Counter():
from collections import Counter
cores = ['vermelho', 'azul', 'vermelho', 'verde', 'azul', 'azul']
contagem = Counter(cores)
print(dict(contagem))
Para operações de cópia de arquivos, ao invés de usar o módulo os.system:
from os import system
system("xcopy e:\\amostra.csv e:\\novodiretorio\\")
Utilize o módulo shutil, que fornece relatórios detalhados de erros e exceções:
import shutil
shutil.copyfile('E:\\dados.csv', 'e:\\backup\\dados.csv')
Erro 4: Não utilizar tratamento de exceções
Tanto desenvolvedores experientes quento iniciantes devem implementar tratamento de exceções em seus códigos para garantir maior robustez. Isso é tipicamente feito com instruções try...except.
Erro 5: Não utilizar geradores
A menos que você precise de uma lista complexa e frequentemente acessada, prefira geradores. Eles economizam significativamente a memória. Compare as duas abordagens:
def potencias_de_dois(limite=20000):
i = 0
potencias = []
while 2**i < limite:
potencias.append(2**i)
i += 1
return potencias
Para dados que são utilizados poucas vezes, ocupam muita memória e são fáceis de gerar, use um gerador:
from itertools import count, takewhile
def potencias_de_dois(limite=20000):
for indice in takewhile(lambda i: 2**i < limite, count(start=0)):
yield 2**indice