List comprehensions: sintaxe e casos de uso

1. Introdução às List Comprehensions

List comprehensions são uma construção sintática poderosa do Python que permite criar listas de forma concisa e expressiva. Elas surgiram como uma alternativa elegante aos loops for tradicionais, inspiradas pela notação de conjuntos da matemática ({x² | x ∈ ℕ}).

A sintaxe básica é surpreendentemente simples:

[expressão for item in iterável]

Para entender o valor das list comprehensions, compare estas duas abordagens equivalentes:

Loop tradicional:

quadrados = []
for x in range(10):
    quadrados.append(x ** 2)

List comprehension:

quadrados = [x ** 2 for x in range(10)]

A versão com comprehension é mais curta, mais legível e mais pythonica. Em vez de três linhas com construção explícita da lista, temos uma única linha que declara claramente a intenção: "crie uma lista com x² para cada x no intervalo de 0 a 9".

2. Estrutura Fundamental da Sintaxe

Toda list comprehension possui três componentes obrigatórios:

  1. Expressão de saída: o valor que será incluído na lista (pode ser qualquer expressão Python)
  2. Variável de iteração: o nome que receberá cada elemento do iterável
  3. Iterável fonte: a sequência sobre a qual iteramos

A ordem de avaliação ocorre da direita para a esquerda: primeiro o for itera sobre o iterável, depois a expressão é avaliada para cada elemento.

Exemplos simples:

# Quadrados perfeitos
quadrados = [n ** 2 for n in range(1, 6)]
print(quadrados)  # [1, 4, 9, 16, 25]

# Extrair vogais de uma string
frase = "Python é incrível"
vogais = [letra for letra in frase if letra.lower() in 'aeiou']
print(vogais)  # ['o', 'é', 'i', 'í', 'e']

3. Filtragem com Condicionais (if)

O poder das list comprehensions se expande significativamente com a adição de filtros condicionais. A sintaxe é:

[expressão for item in iterável if condição]

Casos práticos:

# Números pares até 20
pares = [x for x in range(21) if x % 2 == 0]
print(pares)  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# Strings com mais de 5 caracteres
nomes = ["Ana", "Fernanda", "João", "Sebastião", "Lu"]
nomes_longos = [nome for nome in nomes if len(nome) > 5]
print(nomes_longos)  # ['Fernanda', 'Sebastião']

Diferença crucial: O if no final da comprehension filtra elementos do iterável. Já um if-else dentro da expressão de saída transforma cada elemento:

# if como filtro (remove ímpares)
pares_filtrados = [x for x in range(10) if x % 2 == 0]

# if-else como transformação (classifica todos)
classificacao = ["par" if x % 2 == 0 else "ímpar" for x in range(10)]

4. Aninhamento de Loops em Comprehensions

List comprehensions suportam múltiplos loops aninhados, equivalentes a loops for aninhados tradicionais:

[expressão for a in A for b in B]

Exemplos práticos:

# Produto cartesiano de duas listas
cores = ["vermelho", "azul"]
tamanhos = ["P", "M", "G"]
combinacoes = [(cor, tam) for cor in cores for tam in tamanhos]
print(combinacoes)
# [('vermelho', 'P'), ('vermelho', 'M'), ('vermelho', 'G'),
#  ('azul', 'P'), ('azul', 'M'), ('azul', 'G')]

# Achatar uma matriz (lista de listas)
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
achatada = [num for linha in matriz for num in linha]
print(achatada)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

A ordem dos for na comprehension segue exatamente a mesma ordem dos loops aninhados tradicionais — primeiro o loop mais externo, depois o interno.

5. Uso Avançado com Expressões Complexas

A expressão de saída pode conter chamadas de funções, métodos e construções mais elaboradas:

# Aplicar função a cada elemento
def celsius_para_fahrenheit(c):
    return (c * 9/5) + 32

temperaturas_c = [0, 20, 30, 40]
temperaturas_f = [celsius_para_fahrenheit(c) for c in temperaturas_c]
print(temperaturas_f)  # [32.0, 68.0, 86.0, 104.0]

# Chamada de métodos encadeados
sujos = ["  hello ", "WORLD  ", "  python  "]
limpos = [s.strip().upper() for s in sujos]
print(limpos)  # ['HELLO', 'WORLD', 'PYTHON']

# Combinando com enumerate() e zip()
nomes = ["Alice", "Bob", "Carol"]
idades = [25, 30, 28]
pessoas = [(i, nome, idade) for i, (nome, idade) in enumerate(zip(nomes, idades))]
print(pessoas)  # [(0, 'Alice', 25), (1, 'Bob', 30), (2, 'Carol', 28)]

6. Casos de Uso Práticos no Dia a Dia

Transformação de dados:

# Converter temperaturas
temperaturas = [("São Paulo", 28), ("Rio", 32), ("Curitiba", 18)]
relatorio = [f"{cidade}: {c * 9/5 + 32:.1f}°F" for cidade, c in temperaturas]

# Normalizar valores para 0-1
valores = [10, 25, 40, 55, 70]
min_val, max_val = min(valores), max(valores)
normalizados = [(v - min_val) / (max_val - min_val) for v in valores]

Extração de atributos de objetos:

class Produto:
    def __init__(self, nome, preco, estoque):
        self.nome = nome
        self.preco = preco
        self.estoque = estoque

produtos = [Produto("Notebook", 3500, 10), Produto("Mouse", 50, 100)]
nomes_produtos = [p.nome for p in produtos]
produtos_caros = [p for p in produtos if p.preco > 1000]

Limpeza de dados:

dados_sujos = [10, None, 25, "", 30, 0, None, 15]
dados_limpos = [x for x in dados_sujos if x is not None and x != ""]
# [10, 25, 30, 0, 15]

# Remover duplicatas mantendo ordem
lista = [3, 1, 2, 1, 3, 4, 2, 5]
unicos = []
[unicos.append(x) for x in lista if x not in unicos]
print(unicos)  # [3, 1, 2, 4, 5]

7. Limitações e Boas Práticas

List comprehensions não são adequadas para todas as situações:

Quando evitar:
- Complexidade excessiva: mais de 2-3 níveis de aninhamento
- Efeitos colaterais: comprehensions devem ser usadas para criar listas, não para executar ações
- Lógica muito condicional: múltiplos if-else aninhados na expressão

Mau exemplo (evitar):

# Complexo e difícil de ler
resultado = [
    a if a > b else b 
    for a in range(10) 
    for b in range(10) 
    if a != b and (a + b) % 2 == 0
]

Alternativas recomendadas:
- Generator expressions para grandes volumes: (x**2 for x in range(10**6))
- Loops explícitos para lógica muito condicional ou com efeitos colaterais

8. Considerações de Performance

List comprehensions são geralmente mais rápidas que loops for tradicionais em Python puro, pois a interpretação do bytecode é otimizada para esta construção:

import timeit

# Loop tradicional
def loop_tradicional():
    resultado = []
    for i in range(1000):
        resultado.append(i ** 2)
    return resultado

# List comprehension
def comprehension():
    return [i ** 2 for i in range(1000)]

# A comprehension é tipicamente 10-20% mais rápida

No entanto, é importante considerar o uso de memória: list comprehensions criam a lista completa na RAM. Para conjuntos de dados muito grandes, prefira generator expressions ou loops com yield.

Comparação com map() e filter():

# map + filter
list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, range(10))))

# Comprehension equivalente (mais pythonica)
[x**2 for x in range(10) if x % 2 == 0]

A versão com comprehension é geralmente preferida por ser mais legível e expressiva, seguindo o princípio "explicit is better than implicit" do Zen do Python.

Referências