Lambdas e compreensões

Essas três funcionalidades têm algo em comum: permitem expressar ideias que de outro modo levariam várias linhas em uma única expressão legível. Bem utilizadas, tornam o código mais curto e claro. Mal utilizadas, o tornam ilegível. Este capítulo aborda quando recorrer a cada uma e quando parar.
Funções lambda
Uma lambda é uma função sem nome e com uma única expressão. Você a cria com a palavra-chave lambda. Sua utilidade real é que você pode escrevê-la inline, justamente onde precisa, sem definir uma função nomeada antes. É isso que a torna útil com sorted().
double = lambda x: x * 2
double(5) # 10Isto é equivalente a:
def double(x):
return x * 2Para a maioria dos casos, use def. Lambdas têm uma vantagem real: você pode escrevê-las inline, justamente onde precisa, sem nomeá-las. É isso que as torna úteis com sorted(), map() e filter():
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:
add = lambda a, b: a + b
add(3, 4) # 7Quando usar uma lambda: apenas quando é uma pequena expressão usada em um lugar. Se está crescendo, ou você precisa reutilizá-la, escreva um def apropriado. Uma lambda que abrange vários operadores ou precisa de um condicional é geralmente um sinal para mudar para def.
lambda. Sua razão inteira de existir é ir inline como key= para sorted() para você não definir uma função separada para um trabalho. No momento em que fica maior que uma expressão elegante, escrevo um def real e me sinto melhor por isso. Compreensões de lista
A transformação mais comum em Python: pegue uma sequência, faça algo com cada item, obtenha uma nova lista de volta. Uma compreensão de lista faz isso em uma linha legível: [expression for item in iterable]. Você também pode adicionar um filtro com if.
O caminho longo:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)A compreensão de lista:
squares = [n ** 2 for n in numbers]A estrutura é sempre a mesma: [expression for item in iterable].
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[expression for item in iterable] pega uma sequência, faz uma coisa com cada item e lhe devolve uma nova lista. Leia da esquerda para a direita e diz exatamente o que faz. Essa foi a primeira funcionalidade Python que me fez sentir que estava escrevendo Python em vez de traduzindo de outra linguagem. Filtragem com uma condição
Adicione uma cláusula if para incluir apenas itens que passam em um teste. O resultado é uma nova lista com apenas os itens onde 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 condition ao final para manter apenas os itens que passam no teste: [x for x in data if x > 0]. Qualquer coisa que saia como falsy fica fora da nova lista. A mesma compreensão que você já conhece, com um porteiro nela. 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 naquela 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 para a direita: para cada linha em matrix, para cada item em row, inclua item.
Compreensões aninhadas podem ficar confusas rapidamente. Se leva mais de um momento para parsear, escreva os loops explicitamente.
for em uma compreensão achatam uma lista de listas em uma única lista plana: [item for row in matrix for item in row]. Leia da esquerda para a direita, loop externo primeiro, exatamente a ordem em que escreveria os loops. Se seus olhos travam nela, esse é seu sinal para escrever os loops reais em vez disso. Compreensões de dicionário
Compreensões de dicionário constroem um dicionário em uma expressão, usando a mesma ideia que compreensões de lista: {key: value for item in iterable}. Adicione um filtro com if, o mesmo que com compreensões de lista.
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:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"ana": 87, "carla": 92}words = ["maçã", "banana", "cereja"]
word_lens = {word: len(word) for word in words}
# {"maçã": 4, "banana": 6, "cereja": 6}{key: value for item in iterable} constrói um dicionário em uma linha, mesma forma que uma compreensão de lista com dois-pontos entre chave e valor. Pareie-a com .items() para remodelar um dict que você já tem, ou com zip() para costurar duas listas em um mapeamento. Adicione um if ao final para manter apenas os pares que você quer. Compreensões de conjunto
Compreensões de conjunto constroem um conjunto em uma expressão, com chaves e sem dois-pontos. Como o resultado é um conjunto, duplicatas são automaticamente removidas.
words = ["maçã", "banana", "cereja", "maçã"]
unique = {w.lower() for w in words} # {"maçã", "banana", "cereja"}Use compreensões de conjunto quando você quer valores únicos e não se importa com a ordem.
{expr for item in iterable} com chaves e sem dois-pontos constrói um conjunto, e um conjunto descarta duplicatas grátis. Então se seu trabalho é "me dê os únicos", isso faz em uma linha. Não conte com qualquer ordem particular voltando porém. Expressões geradoras
Geradores parecem compreensões de lista com parênteses em vez de colchetes. A diferença-chave: uma compreensão de lista constrói toda a lista na 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.
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum() consome o geradorQuando passando um gerador direto para uma função como sum(), max(), min() ou any(), você pode descartar os parênteses extra:
total = sum(n ** 2 for n in range(1000)) # um conjunto de parênteses, não doisPara a maioria do código diário, compreensões de lista são boas. Use geradores quando você está processando grandes datasets ou dados em streaming onde manter tudo na memória seria desperdiçador.
sum() ou max() e pule construir a lista inteiramente. zip()
zip() pareia itens de duas ou mais sequências juntos para que você possa iterá-los em paralelo. Para na sequência mais curta. É a forma limpa de evitar gerenciar índices quando duas listas se correspondem uma à outra.
names = ["Ana", "Bruno", "Carla"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Ana: 87
# Bruno: 74
# Carla: 92zip() para na sequência mais curta. Se suas sequências podem ter comprimentos diferentes, use itertools.zip_longest() com um valor de preenchimento.
Para converter de volta de uma lista zipped de pares em duas listas separadas, use zip(*pairs):
pairs = [("Ana", 87), ("Bruno", 74), ("Carla", 92)]
names, scores = zip(*pairs)
# names = ("Ana", "Bruno", "Carla")
# scores = (87, 74, 92)*pairs desempacota a lista em argumentos separados, então zip(*pairs) se torna zip(("Ana", 87), ("Bruno", 74), ("Carla", 92)). O operador * é coberto no capítulo de Funções.
zip() também é a forma limpa de iterar múltiplas 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() pareia duas ou mais sequências para que você possa iterá-las juntas, sem malabarismo de índice. Para na mais curta, então comprimentos desiguais silenciosamente perdem os extras. E zip(*pairs) roda ao contrário, dividindo uma lista de tuplas de volta em listas separadas. map() e filter()
map() e filter() são ferramentas de estilo funcional mais antigas que fazem o que 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.
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ê tem uma função nomeada que já existe:
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3] (mais limpo que uma compreensão aqui)map(func, iterable) roda uma função sobre cada item; filter(func, iterable) mantém apenas os itens onde a função retorna truthy. Elas são a forma mais antiga de fazer o que compreensões fazem, então você as encontrará em código de outras pessoas. Para seu próprio, uma compreensão lê mais clara para a maioria. Na prática
Filtre uma lista de jogadores para pontuações de aprovação, ordene por pontuação com sorted e uma lambda, depois imprima com posições enumeradas:
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}")
