Lambdas e comprehensions
Esses três recursos têm algo em comum: eles permitem expressar ideias que, de outra forma, exigiriam várias linhas, em uma única expressão legível. Bem usados, eles deixam o código mais curto e mais claro. Mal usados, deixam ilegível. Este capítulo mostra quando usar cada um deles e quando parar.
Funções lambda
Uma lambda é uma função sem nome, de uma única expressão. Você cria uma com a palavra-chave lambda. Sua real utilidade é que você pode escrevê-la inline, exatamente onde precisa dela, sem antes definir uma função nomeada. É isso que a torna útil com sorted().
double = lambda x: x * 2
double(5) # 10Isso é equivalente a:
def double(x):
return x * 2Na maioria dos casos, use def. Lambdas têm uma vantagem real: você pode escrevê-las inline, exatamente onde precisa delas, sem precisar nomeá-las. É isso que as torna úteis com sorted(), map() e filter():
players = [("Alice", 87), ("Bob", 74), ("Carol", 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 só para o argumento key=. A lambda mantém a intenção local e visível.
Lambdas podem receber vários argumentos:
add = lambda a, b: a + b
add(3, 4) # 7Quando usar uma lambda: só quando for uma expressão pequena, usada em um único lugar. Se ela estiver crescendo, ou se você precisar reutilizá-la, escreva um def de verdade. Uma lambda que se estende por vários operadores ou que precisa de uma condicional geralmente é sinal de que é hora de trocar para def.
lambda. Todo o motivo dela existir é entrar inline como key= num sorted(), pra você não precisar definir uma função separada só para uma tarefa. No instante em que ela cresce além de uma expressão bem enxuta, eu escrevo um def de verdade e me sinto melhor com isso. List comprehensions
A transformação mais comum no Python: pegar uma sequência, fazer algo com cada item e obter uma nova lista como resultado. Uma list comprehension faz isso em uma única linha legível: [expressão for item in iterável]. Você também pode adicionar um filtro com if.
O jeito mais longo:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)A list comprehension:
squares = [n ** 2 for n in numbers]A estrutura é sempre a mesma: [expressão for item in iterável].
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[expressão for item in iterável] pega uma sequência, faz uma coisa com cada item e te devolve uma nova lista. Leia da esquerda pra direita e ela diz exatamente o que faz. Esse foi o primeiro recurso do Python que me fez sentir que eu estava escrevendo Python, e não traduzindo de outra linguagem. Filtrando com uma condição
Adicione uma cláusula if para incluir apenas os itens que passam por um teste. O resultado é uma nova lista com apenas os itens em que a condição é True.
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]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]if condição no final para manter apenas os itens que passam pelo teste: [x for x in data if x > 0]. Tudo que for avaliado como falso fica de fora da nova lista. A mesma comprehension que você já conhece, agora com um porteiro. Comprehensions aninhadas
Você pode aninhar comprehensions para achatar uma lista de listas em uma única lista. Leia da esquerda pra direita: para cada linha, para cada item nessa linha, inclua o item.
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 pra direita: para cada linha em matrix, para cada item na linha, inclua item.
Comprehensions aninhadas podem confundir rápido. Se demorar mais que um instante para entender, escreva os loops explicitamente.
for em uma comprehension achatam uma lista de listas em uma única lista plana: [item for row in matrix for item in row]. Leia da esquerda pra direita, loop externo primeiro, exatamente a ordem em que você escreveria os loops. Se seus olhos travarem nela, essa é a deixa para escrever os loops de verdade. Dict comprehensions
Dict comprehensions constroem um dicionário em uma única expressão, usando a mesma ideia das list comprehensions: {chave: valor for item in iterável}. Adicione um filtro com if, do mesmo jeito que nas list comprehensions.
names = ["alice", "bob", "carol"]
scores = [87, 74, 92]
score_map = {name: score for name, score in zip(names, scores)}
# {"alice": 87, "bob": 74, "carol": 92}Com um filtro:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"alice": 87, "carol": 92}words = ["apple", "banana", "cherry"]
word_lens = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}{chave: valor for item in iterável} constrói um dicionário em uma linha, mesmo formato de uma list comprehension com dois-pontos entre chave e valor. Combine com .items() para remodelar um dict que você já tem, ou com zip() para costurar duas listas em um único mapeamento. Adicione um if no final para manter só os pares que você quer. Set comprehensions
Set comprehensions constroem um conjunto (set) em uma única expressão, com chaves e sem dois-pontos. Como o resultado é um set, duplicatas são removidas automaticamente.
words = ["apple", "banana", "cherry", "apple"]
unique = {w.lower() for w in words} # {"apple", "banana", "cherry"}Use set comprehensions quando quiser valores únicos e não se importar com a ordem.
{expr for item in iterável} com chaves e sem dois-pontos constrói um set, e um set descarta duplicatas de graça. Então, se sua tarefa é "me dê os únicos", isso faz o serviço em uma linha. Só não conte com nenhuma ordem específica no retorno. Generator expressions
Generators parecem list comprehensions com parênteses em vez de colchetes. A diferença fundamental: uma list comprehension constrói a lista inteira na memória de uma vez. Um generator produz valores um por um, apenas quando necessário. Para sequências grandes, isso usa muito menos memória.
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum() consome o generatorAo passar um generator diretamente para uma função como sum(), max(), min() ou any(), você pode omitir os parênteses extras:
total = sum(n ** 2 for n in range(1000)) # um par de parênteses, não doisPara a maior parte do código do dia a dia, list comprehensions são suficientes. Use generators quando estiver processando grandes conjuntos de dados ou dados em streaming, onde manter tudo na memória seria um desperdício.
sum() ou max() e pule construir a lista de vez. zip()
zip() agrupa itens de duas ou mais sequências para que você possa percorrê-las em paralelo. Ele para na sequência mais curta. É a forma limpa de evitar gerenciar índices quando duas listas se correspondem entre si.
names = ["Alice", "Bob", "Carol"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Alice: 87
# Bob: 74
# Carol: 92zip() para na sequência mais curta. Se suas sequências podem ter tamanhos diferentes, use itertools.zip_longest() com um valor de preenchimento.
Para converter de volta uma lista de pares zipada para duas listas separadas, use zip(*pairs):
pairs = [("Alice", 87), ("Bob", 74), ("Carol", 92)]
names, scores = zip(*pairs)
# names = ("Alice", "Bob", "Carol")
# scores = (87, 74, 92)*pairs desempacota a lista em argumentos separados, então zip(*pairs) se torna zip(("Alice", 87), ("Bob", 74), ("Carol", 92)). O operador * é abordado no capítulo Funções.
zip() também é a forma limpa de iterar várias sequências em paralelo sem gerenciar índices manualmente:
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})")zip() combina duas ou mais sequências para que você possa percorrê-las juntas, sem malabarismo com índices. Ele para na mais curta, então tamanhos diferentes fazem os extras desaparecerem silenciosamente. E zip(*pairs) faz o caminho de volta, dividindo uma lista de tuplas de novo em listas separadas. map() e filter()
map() e filter() são ferramentas mais antigas, de estilo funcional, que fazem o que as comprehensions fazem. Você vai encontrá-las em código mais antigo, então vale a pena saber o que elas significam. Prefira comprehensions para código novo; elas são mais legíveis para a maioria dos desenvolvedores 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 comprehensions; elas são mais legíveis para a maioria dos desenvolvedores Python. Use map() quando você tiver uma função nomeada que já existe:
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3] (mais limpo do que uma comprehension aqui)map(func, iterável) executa uma função sobre cada item; filter(func, iterável) mantém só os itens em que a função retorna truthy. São a forma mais antiga de fazer o que as comprehensions fazem, então você vai encontrá-las no código de outras pessoas. Para o seu próprio código, uma comprehension se lê mais clara para a maioria das pessoas. Na prática
Filtre uma lista de jogadores para as pontuações aprovadas, classifique por pontuação com sorted e uma lambda, depois imprima com posições numeradas:
players = [
{"name": "Alice", "score": 87},
{"name": "Bob", "score": 42},
{"name": "Carol", "score": 96},
{"name": "Dave", "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}")
