Compreensões (comprehensions) são uma construção sintática poderosa em Python que permite criar sequências de forma concisa e expressiva. Elas substituem loops tradicionais de mapeamento e filtragem, tornando o código mais legível e, em muitos casos, mais rápido. Nesta aula, exploraremos comprehensions para listas, dicionários e conjuntos, além de generator expressions e como aninhá-las.

Dominar comprehensions é essencial para escrever código Python idiomático. Elas são amplamente utilizadas em projetos reais, desde processamento de dados até scripts de automação. Vamos ver cada tipo com exemplos práticos.

List, dict e set comprehensions

As comprehensions permitem criar listas, dicionários e conjuntos em uma única linha, com uma sintaxe que lembra a notação matemática de conjuntos. A estrutura básica é: [expressão for item in iterável if condição] para listas; {chave: valor for item in iterável if condição} para dicionários; e {expressão for item in iterável if condição} para conjuntos.

Por exemplo, para criar uma lista de quadrados dos números pares de 0 a 9, podemos usar uma list comprehension em vez de um loop for com append:

# Loop tradicional
quadrados = []
for i in range(10):
    if i % 2 == 0:
        quadrados.append(i**2)
print(quadrados)  # [0, 4, 16, 36, 64]

# List comprehension
quadrados = [i**2 for i in range(10) if i % 2 == 0]
print(quadrados)  # [0, 4, 16, 36, 64]

Para dicionários, podemos criar um mapeamento de números para seus cubos apenas para números ímpares:

cubos = {i: i**3 for i in range(10) if i % 2 != 0}
print(cubos)  # {1: 1, 3: 27, 5: 125, 7: 343, 9: 729}

E para conjuntos, removendo duplicatas automaticamente:

letras = 'abracadabra'
unicas = {letra for letra in letras}
print(unicas)  # {'a', 'b', 'c', 'd', 'r'}

Generator expressions

Generator expressions são semelhantes às list comprehensions, mas usam parênteses em vez de colchetes e produzem um objeto gerador, que é lazy: ele gera os valores sob demanda, sem armazenar toda a sequência na memória. Isso é útil para grandes volumes de dados.

Exemplo: somar os quadrados de 1 a 1.000.000 sem criar uma lista gigante:

soma = sum(x**2 for x in range(1, 1000001))
print(soma)  # 333333833333500000

Note que não há colchetes dentro do sum, apenas parênteses. Isso é uma generator expression. Você também pode usar generator expressions em funções que aceitam iteráveis, como any, all, min, max.

Para iterar explicitamente sobre um gerador, você pode usar um loop for ou convertê-lo para lista (perdendo a vantagem de memória):

gen = (x**2 for x in range(5))
for valor in gen:
    print(valor)

Aninhamento

Compreensões podem ser aninhadas, ou seja, você pode ter múltiplos for dentro de uma comprehension. Isso é útil para trabalhar com estruturas aninhadas, como matrizes.

Por exemplo, para achatar uma matriz 2D em uma lista:

matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
plana = [num for linha in matriz for num in linha]
print(plana)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

A ordem dos loops na comprehension segue a mesma ordem que em loops aninhados tradicionais: primeiro o loop externo, depois o interno. Você também pode adicionar condições. Por exemplo, para obter apenas números pares da matriz:

pares = [num for linha in matriz for num in linha if num % 2 == 0]
print(pares)  # [2, 4, 6, 8]

Aninhamento também funciona para dicionários e conjuntos. Por exemplo, criar um dicionário com pares (linha, coluna) para elementos não nulos:

matriz = [[1, 0, 3], [0, 5, 0]]
nao_nulos = {(i, j): valor for i, linha in enumerate(matriz) for j, valor in enumerate(linha) if valor != 0}
print(nao_nulos)  # {(0, 0): 1, (0, 2): 3, (1, 1): 5}

Legibilidade

Embora comprehensions sejam concisas, abusar delas pode prejudicar a legibilidade. Uma boa prática é limitar o aninhamento a, no máximo, dois níveis. Se a lógica ficar muito complexa, prefira loops explícitos ou funções auxiliares.

Por exemplo, esta comprehension é difícil de ler:

result = [a for b in c for d in e if f(d) for g in h if i(g)]

Em vez disso, divida em etapas ou use loops. Além disso, evite efeitos colaterais dentro de comprehensions (como print). Comprehensions são para construir sequências, não para executar ações.

Outra dica: use nomes de variáveis descritivos. Por exemplo, [num * 2 for num in numeros] é mais claro que [x * 2 for x in y].

Boas práticas e observações finais

Prefira list comprehensions a map e filter quando a lógica envolver lambdas simples, pois comprehensions são mais legíveis. Use generator expressions para grandes volumes de dados para economizar memória. Lembre-se de que comprehensions criam uma nova sequência; se você precisar modificar uma existente, loops podem ser mais adequados.

Referências

Exercícios

  1. Crie uma list comprehension que gere uma lista dos quadrados dos números ímpares de 1 a 20.

    ✓ Resposta:
    [i**2 for i in range(1, 21) if i % 2 != 0]
  2. Dada uma lista de palavras, crie um dicionário que mapeie cada palavra ao seu comprimento, apenas para palavras com mais de 3 letras.

    ✓ Resposta:
    palavras = ['casa', 'sol', 'python', 'a', 'programacao']
    comprimentos = {palavra: len(palavra) for palavra in palavras if len(palavra) > 3}
    print(comprimentos)  # {'casa': 4, 'python': 6, 'programacao': 11}
  3. Use uma generator expression para calcular a soma dos cubos dos números de 1 a 1000.

    ✓ Resposta:
    soma = sum(x**3 for x in range(1, 1001))
    print(soma)  # 250500250000
  4. Dada uma matriz 3x3 (lista de listas), use uma list comprehension aninhada para extrair todos os elementos da diagonal principal.

    ✓ Resposta:
    matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    diagonal = [matriz[i][i] for i in range(len(matriz))]
    print(diagonal)  # [1, 5, 9]
  5. Reescreva o seguinte loop usando uma set comprehension: letras_unicas = set()
    for char in 'banana':
    if char not in 'aeiou':
    letras_unicas.add(char)

    ✓ Resposta:
    letras_unicas = {char for char in 'banana' if char not in 'aeiou'}
    print(letras_unicas)  # {'b', 'n'}