Skip to content
This page has been auto-translated and may contain errors.View in English

Lambdas e compreensões

Esses três recursos têm algo em comum: permitem expressar ideias que de outra forma exigiriam várias linhas em uma única expressão legível. Bem usados, deixam o código mais curto e claro. Mal usados, tornam-no ilegível. Este capítulo aborda quando recorrer a cada um deles e quando parar.

Lambdas, compreensões e zip são três ferramentas que comprimem padrões comuns em expressões. Não são obrigatórios, mas aparecem em todo o código Python e vale a pena reconhecê-los e escrevê-los fluentemente. O princípio orientador: use-os quando deixarem a intenção mais clara, não apenas mais curta.

Expressões lambda criam objetos de função anônimos em tempo de execução. Compreensões compilam para bytecode otimizado que constrói coleções sem um laço for no quadro externo. Geradores são preguiçosos: produzem valores sob demanda sem materializar a sequência inteira. zip retorna um iterador de tuplas, consumindo os iteráveis de entrada de forma preguiçosa. Os três compartilham o tema de expressar transformações como expressões em vez de laços imperativos.

Funções lambda

Uma lambda é uma função sem nome, de uma única expressão. Você a cria com a palavra-chave lambda. Sua real utilidade é que você pode escrevê-la inline, exatamente onde precisa, sem antes definir uma função nomeada. É isso que a torna útil com sorted().

Uma lambda é uma função anônima de expressão única. Pode receber múltiplos argumentos, mas seu corpo deve ser uma única expressão, não uma instrução. Seu uso principal é como argumento inline key= ou callback, onde um def completo adicionaria indireção desnecessária. Para algo mais complexo, use def.

lambda args: expression compila para um objeto de código e cria um objeto de função, idêntico a def exceto por não ter nome (aparece como <lambda> em tracebacks), não poder conter instruções e não suportar docstrings ou anotações. Lambdas participam de closure: variáveis livres são capturadas do escopo envolvente. A armadilha comum: lambda i: i em um laço captura i por referência, não por valor; use lambda i=i: i para vincular o valor no momento da criação.

python
double = lambda x: x * 2
double(5)   # 10

Isso equivale a:

python
def double(x):
    return x * 2

Na maioria dos casos, use def. Lambdas têm uma vantagem real: você pode escrevê-las inline, exatamente onde precisa, sem nomeá-las. É isso que as torna úteis com sorted(), map() e filter():

python
players = [("Ana", 87), ("Bruno", 74), ("Carla", 92)]

sorted(players, key=lambda p: p[1])              # ordena por pontuação (crescente)
sorted(players, key=lambda p: p[1], reverse=True)  # ordena por pontuação (decrescente)

Sem uma lambda, você teria que definir uma função nomeada apenas para o argumento key=. A lambda mantém a intenção local e visível.

Lambdas podem receber múltiplos argumentos:

python
add = lambda a, b: a + b
add(3, 4)   # 7

Quando usar uma lambda: apenas quando for uma expressão simples usada em um único lugar. Se estiver ficando complexa, ou se você precisar reutilizá-la, escreva um def adequado. Uma lambda que se estende por vários operadores ou requer condicionais geralmente é um sinal para mudar para def.

Compreensões de lista

A transformação mais comum em Python: pegar uma sequência, fazer algo com cada item e obter uma nova lista. Uma compreensão de lista faz isso em uma linha legível: [expressão for item in iterável]. Você também pode adicionar um filtro com if.

Compreensões de lista são uma substituição concisa para o padrão de construção com laço. Compilam para bytecode otimizado e geralmente são mais rápidas que laços for equivalentes com .append(). A estrutura é [expressão for item in iterável if condição]; a cláusula if é opcional.

Compreensões de lista compilam para um laço LIST_APPEND em bytecode dedicado, mais rápido que chamadas repetidas a list.append() em um laço de nível Python. Criam um novo escopo no Python 3 (ao contrário do Python 2), então a variável do laço não vaza. Compreensões aninhadas executam da esquerda para a direita e de cima para baixo: [expr for x in a for y in b] é equivalente a um laço for aninhado com x como laço externo.

O jeito longo:

python
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
    squares.append(n ** 2)

A compreensão de lista:

python
squares = [n ** 2 for n in numbers]

A estrutura é sempre a mesma: [expressão for item in iterável].

python
scores    = [87, 42, 96, 55, 71]
scaled    = [s * 1.1 for s in scores]       # aplica um bônus de 10%
as_grades = [f"{s}/100" for s in scores]    # formata cada um

Filtrando com uma condição

Adicione uma cláusula if para incluir apenas itens que passem em um teste. O resultado é uma nova lista apenas com os itens em que a condição é True.

A cláusula if em uma compreensão é um filtro, não um if/else. Executa uma vez por item e inclui apenas itens para os quais a condição é verdadeira. Para uma transformação condicional (mapear um valor para outro com base em uma condição), use uma expressão ternária dentro da expressão principal.

O filtro if é distinto de uma expressão condicional na saída. [x for x in data if x > 0] filtra. [x if x > 0 else 0 for x in data] mapeia (limita a zero). Você pode combinar ambos: [x * 2 for x in data if x > 0]. Múltiplas cláusulas if se encadeiam com and implícito.

python
numbers  = [1, 2, 3, 4, 5, 6, 7, 8]
evens    = [n for n in numbers if n % 2 == 0]    # [2, 4, 6, 8]
odds     = [n for n in numbers if n % 2 != 0]    # [1, 3, 5, 7]
python
scores   = [87, 42, 96, 55, 71, 38]
passing  = [s for s in scores if s >= 60]    # [87, 96, 71]
failing  = [s for s in scores if s < 60]     # [42, 55, 38]

Compreensões aninhadas

Você pode aninhar compreensões para achatar uma lista de listas em uma única lista. Leia da esquerda para a direita: para cada linha, para cada item dessa linha, inclua o item.

Compreensões aninhadas executam da esquerda para a direita. A primeira cláusula for é o laço externo, a segunda é o interno. Produzem um único resultado plano, não uma estrutura 2D. Se a compreensão for difícil de ler à primeira vista, escreva os laços explicitamente.

Compreensões aninhadas executam como laços aninhados com o primeiro for como o mais externo. O escopo de cada variável de laço está disponível para as cláusulas seguintes. Para produtos cartesianos, itertools.product muitas vezes é mais claro. A regra-chave de legibilidade: se analisar a compreensão leva mais de um segundo, a forma explícita do laço é melhor documentação.

python
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat   = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Leia da esquerda para a direita: para cada linha em matrix, para cada item em linha, inclua item.

Compreensões aninhadas podem ficar confusas rapidamente. Se levar mais que um instante para entender, escreva os laços explicitamente.

Compreensões de dicionário

Compreensões de dicionário constroem um dicionário em uma única expressão, usando a mesma ideia das compreensões de lista: {chave: valor for item in iterável}. Adicione um filtro com if, assim como nas compreensões de lista.

Compreensões de dicionário criam um novo dict a partir de qualquer iterável que produza pares chave-valor. A sintaxe é {key_expr: val_expr for item in iterável if condição}. Chaves duplicadas vindas do laço usam o último valor, silenciosamente. .items() em um dict existente é o iterável de origem mais comum para compreensões de dicionário.

Compreensões de dicionário compilam para bytecode MAP_ADD dedicado, análogo a LIST_APPEND para compreensões de lista. Criam um novo escopo no Python 3. Expressões de chave devem produzir valores hasheáveis; se uma expressão de chave produz duplicata, o valor posterior vence silenciosamente. Para semântica de mesclagem ordenada, o operador | (Python 3.9+) é mais limpo que uma compreensão.

python
names  = ["ana", "bruno", "carla"]
scores = [87, 74, 92]

score_map = {name: score for name, score in zip(names, scores)}
# {"ana": 87, "bruno": 74, "carla": 92}

Com um filtro:

python
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"ana": 87, "carla": 92}
python
words     = ["maçã", "banana", "cereja"]
word_lens = {word: len(word) for word in words}
# {"maçã": 4, "banana": 6, "cereja": 6}

Compreensões de conjunto

Compreensões de conjunto constroem um conjunto em uma única expressão, com chaves e sem dois-pontos. Como o resultado é um conjunto, duplicatas são removidas automaticamente.

Compreensões de conjunto usam {expressão for item in iterável} e produzem um set. Eliminam duplicatas automaticamente. Use-as quando precisar de uma coleção única construída a partir de uma transformação, onde a ordem não importa.

Compreensões de conjunto compilam para o bytecode SET_ADD. O resultado é um conjunto não ordenado: valores duplicados da expressão são silenciosamente mesclados. Compreensões de conjunto são menos comuns que as de lista ou dicionário, mas são a forma limpa de produzir uma transformação sem duplicatas em uma única expressão.

python
words   = ["maçã", "banana", "cereja", "maçã"]
unique  = {w.lower() for w in words}    # {"maçã", "banana", "cereja"}

Use compreensões de conjunto quando quiser valores únicos e não se importar com a ordem.

Expressões geradoras

Geradores se parecem com compreensões de lista, mas com parênteses em vez de colchetes. A diferença-chave: uma compreensão de lista constrói toda a lista em memória de uma vez. Um gerador produz valores um de cada vez, apenas quando necessário. Para sequências grandes, isso usa muito menos memória.

Uma expressão geradora produz um iterador, não uma coleção. Calcula valores de forma preguiçosa: o próximo valor só é produzido quando solicitado. Isso é mais valioso quando o resultado é consumido imediatamente por uma função como sum(), max() ou any(), sem sentido em construir a lista completa primeiro.

Expressões geradoras compilam para um objeto de código e retornam um objeto gerador. Valores são produzidos preguiçosamente via __next__, tornando o uso de memória O(1) independentemente do tamanho da entrada. Participam do protocolo de iterador e podem ser encadeadas. Quando passadas diretamente para uma função que aceita um iterável, os parênteses externos podem ser omitidos. Geradores não podem ser reutilizados após esgotamento; se você precisar iterar múltiplas vezes, materialize em uma lista.

python
squares_gen = (n ** 2 for n in range(1000000))
python
total = sum(n ** 2 for n in range(1000000))   # sum() consome o gerador

Ao passar um gerador diretamente para uma função como sum(), max(), min() ou any(), você pode dispensar os parênteses extras:

python
total = sum(n ** 2 for n in range(1000))   # um par de parênteses, não dois

Para a maioria do código do dia a dia, compreensões de lista servem bem. Use geradores quando estiver processando grandes conjuntos de dados ou dados em streaming, onde manter tudo em memória seria um desperdício.

zip()

zip() combina itens de duas ou mais sequências para que você possa percorrê-las em paralelo. Para na sequência mais curta. É a forma limpa de evitar gerenciar índices quando duas listas se correspondem.

zip() retorna um iterador preguiçoso de tuplas, consumindo seus iteráveis de entrada em sincronia. Para na entrada mais curta: sequências mais longas são silenciosamente truncadas. Para sequências que podem ter tamanhos diferentes, itertools.zip_longest() preenche as mais curtas com um valor especificado.

zip() retorna um objeto zip, um iterador preguiçoso que chama next() em cada iterador de entrada simultaneamente. Para quando qualquer iterador lança StopIteration. Todas as entradas são consumidas preguiçosamente: zip() em si aloca memória O(1) independentemente do tamanho da entrada. zip(*iterável) é a operação padrão de transposição; * desempacota o iterável externo em argumentos separados.

python
names  = ["Ana", "Bruno", "Carla"]
scores = [87, 74, 92]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Ana: 87
# Bruno: 74
# Carla: 92

zip() para na sequência mais curta. Se suas sequências puderem ter tamanhos diferentes, use itertools.zip_longest() com um valor de preenchimento.

Para converter de volta de uma lista zipada de pares para duas listas separadas, use zip(*pairs):

python
pairs  = [("Ana", 87), ("Bruno", 74), ("Carla", 92)]
names, scores = zip(*pairs)
# names = ("Ana", "Bruno", "Carla")
# scores = (87, 74, 92)

O que o * faz aqui?

*pairs desempacota a lista em argumentos separados: zip(*pairs) se torna zip(("Ana", 87), ("Bruno", 74), ("Carla", 92)). O operador * é abordado no capítulo Funções.

zip() também é a forma limpa de iterar múltiplas sequências em paralelo sem gerenciar índices manualmente:

python
before = [10, 20, 30]
after  = [15, 18, 35]

for b, a in zip(before, after):
    change = a - b
    print(f"{b} -> {a} ({'+' if change >= 0 else ''}{change})")

map() e filter()

map() e filter() são ferramentas mais antigas, de estilo funcional, que fazem o que as compreensões fazem. Você as verá em código mais antigo, então vale a pena saber o que significam. Prefira compreensões para código novo; são mais legíveis para a maioria dos desenvolvedores Python.

map(func, iterável) retorna um iterador preguiçoso que aplica func a cada item. filter(func, iterável) retorna um iterador preguiçoso de itens para os quais func é verdadeiro. Ambos antecedem as compreensões. Prefira compreensões em código novo; use map() quando você já tem uma função nomeada que faz o que precisa.

map() e filter() retornam iteradores preguiçosos (não listas) no Python 3. map(f, it) é equivalente a (f(x) for x in it). filter(pred, it) é equivalente a (x for x in it if pred(x)). Para funções nomeadas, list(map(int, strings)) é idiomático porque se lê como "mapear int sobre strings"; a compreensão equivalente [int(s) for s in strings] é igualmente válida.

python
numbers = [1, 2, 3, 4, 5]

list(map(lambda x: x ** 2, numbers))         # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4]

Prefira compreensões; são mais legíveis para a maioria dos desenvolvedores Python. Use map() quando você já tem uma função nomeada que existe:

python
strings = ["1", "2", "3"]
numbers = list(map(int, strings))   # [1, 2, 3] (mais limpo que uma compreensão aqui)

Na prática

Filtre uma lista de jogadores pelas pontuações aprovadas, ordene por pontuação com sorted e uma lambda, depois imprima com posições enumeradas:

python
players = [
    {"name": "Ana", "score": 87},
    {"name": "Bruno",   "score": 42},
    {"name": "Carla", "score": 96},
    {"name": "Diego",  "score": 55},
]

passing   = [p for p in players if p["score"] >= 60]
ranked    = sorted(passing, key=lambda p: p["score"], reverse=True)
score_map = {p["name"]: p["score"] for p in ranked}

for i, (name, score) in enumerate(score_map.items(), start=1):
    print(f"{i}. {name}: {score}")

Filtre uma lista de usuários por admins ativos, construa um dicionário de busca de id-para-nome e colete nomes ordenados em uma única passagem cada:

python
raw_users = [
    {"id": 1, "name": "Ana", "role": "admin", "active": True},
    {"id": 2, "name": "Bruno",   "role": "user",  "active": False},
    {"id": 3, "name": "Carla", "role": "admin", "active": True},
    {"id": 4, "name": "Diego",  "role": "user",  "active": True},
]

active_admins = [u for u in raw_users if u["active"] and u["role"] == "admin"]
id_map        = {u["id"]: u["name"] for u in raw_users}
names         = sorted(u["name"] for u in raw_users if u["active"])

print(f"Active admins: {[u['name'] for u in active_admins]}")
print(f"All active: {names}")

Combine nomes de features com pontuações de importância usando zip, construa uma compreensão de dicionário, ordene com uma lambda e normalize valores em uma segunda compreensão:

python
feature_names = ["age", "income", "score", "tenure"]
importances   = [0.12, 0.34, 0.28, 0.26]

feat_dict = {f: i for f, i in zip(feature_names, importances)}
top_feats = sorted(feat_dict.items(), key=lambda x: x[1], reverse=True)[:2]

print("Top 2 features:")
for name, score in top_feats:
    print(f"  {name}: {score:.2f}")

# Normaliza para somar 1.0 (os valores já somam 1 aqui, mas mostrado como padrão)
total      = sum(feat_dict.values())
normalised = {k: round(v / total, 4) for k, v in feat_dict.items()}
print(f"Normalised: {normalised}")

zip combina as duas listas sem construir tuplas intermediárias. A compreensão de dicionário constrói o mapeamento em uma única expressão. A lambda de ordenação evita uma função nomeada para a chave. A compreensão de normalização transforma valores sem mutar o dicionário original.