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

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.

Lambdas, comprehensions e zip são três ferramentas que comprimem padrões comuns em expressões. Não são obrigatórias, mas aparecem em todo tipo de código Python e vale a pena saber reconhecê-las e escrevê-las com fluência. O princípio que deve guiar seu uso: use-as quando deixarem a intenção mais clara, não apenas mais curta.

Todas essas ferramentas expressam uma transformação como uma expressão (um trecho de código que é avaliado e resulta em um valor), em vez de um loop imperativo que constrói um resultado passo a passo. Lambdas te dão uma pequena função sem nome, escrita inline. Comprehensions constroem uma coleção em uma única passagem. Generators são lazy (produzem valores sob demanda, em vez de todos de uma vez), então mantêm apenas um item na memória em vez da sequência inteira. zip percorre várias sequências em paralelo. O resultado é um código que se lê como "o que eu quero", e não como "como montar isso" — desde que você pare antes que a linha única deixe de ser legível.

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().

Uma lambda é uma função anônima de expressão única. Ela pode receber vários argumentos, mas seu corpo precisa ser uma única expressão, não uma instrução (statement). Seu uso principal é como argumento inline de key= ou callback, onde um def completo adicionaria uma indireção desnecessária. Para qualquer coisa mais complexa, use def.

Uma lambda é o mesmo tipo de objeto de função que um def produz, com três limitações: ela não tem nome (aparece como <lambda> em um traceback, o relatório de erro que o Python imprime quando algo levanta uma exceção), seu corpo é uma única expressão, então não pode conter instruções, e ela não carrega docstring. A pegadinha que costuma custar um bom tempo de depuração é o late binding (vinculação tardia) em uma closure (uma função que captura uma variável do escopo em que foi criada). Uma lambda criada dentro de um loop captura a variável do loop por referência, então todas as lambdas acabam vendo o valor final da variável, não o valor que ela tinha no momento em que a lambda foi criada:

python
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2], e não [0, 1, 2]

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])        # [0, 1, 2]: i=i fixa o valor agora

O truque i=i fixa o valor atual como argumento padrão no momento da criação. Use-o sempre que construir funções dentro de um loop.

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

Isso é equivalente 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 delas, sem precisar nomeá-las. É isso que as torna úteis com sorted(), map() e filter():

python
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:

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

Quando 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.

JunoFunções lambda Uma lambda é uma funçãozinha de uma linha, sem nome, escrita com a palavra-chave 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.
JunoFunções lambda Uma lambda é uma função anônima de expressão única, útil como key= inline ou callback, onde um def nomeado só adicionaria ruído. O corpo é uma única expressão, nenhuma instrução é permitida. Qualquer coisa mais elaborada, use def.
JunoFunções lambda Mesmo objeto de função que um def, só sem nome, sem instruções e sem docstring, então mantenha-as como um key= de uma linha só. A armadilha é o late binding: uma lambda criada dentro de um loop lê o valor final da variável do loop, então escreva lambda i=i: i para fixá-lo. Essa aí pega todo mundo, exatamente uma vez.

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.

List comprehensions são uma forma concisa de substituir o padrão de construir uma lista com um loop. Em geral, são mais rápidas do que o for equivalente com .append(), já que o Python não chama um método a cada passagem. A estrutura é [expressão for item in iterável if condição]; a cláusula if é opcional.

Uma comprehension é sensivelmente mais rápida do que o mesmo loop chamando .append() a cada passagem, o que a torna a forma idiomática de construir uma lista a partir de uma transformação. Ela também roda em seu próprio escopo (a região onde um nome é visível), o que significa que a variável do loop não escapa para o código ao redor: depois de [n ** 2 for n in numbers], o nome n não existe fora da comprehension. A armadilha a observar é usar uma comprehension quando o corpo faz trabalho de verdade. Assim que a expressão crescer além de uma transformação simples, ou você quiser adicionar logging ou um bloco try, o loop explícito é a ferramenta melhor, porque uma comprehension só pode conter uma expressão, nunca uma instrução.

O jeito mais longo:

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

A list comprehension:

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
JunoList comprehensions[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.
JunoList comprehensions[expressão for item in iterável] substitui o padrão de construir-com-loop-e-.append(), e fica mais limpo fazendo isso. Ela produz uma lista nova e não modifica nada. Mantenha a expressão simples, ou a legibilidade que você ganhou se perde.
JunoList comprehensions Mais rápida que o loop com .append() e roda em seu próprio escopo, então a variável do loop nunca escapa. A linha a não cruzar: uma comprehension recebe uma expressão, não uma instrução, então no segundo em que você quiser um bloco try ou uma linha de log, o loop explícito ganha.

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.

A cláusula if em uma comprehension é um filtro, não um if/else. Ela é executada uma vez por item e inclui apenas os itens para os quais a condição é truthy (avaliada como 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.

A posição do if decide se você está filtrando ou transformando, e confundir as duas coisas é uma causa comum de um resultado com o tamanho errado. Um if no final filtra: [x for x in data if x > 0] descarta itens. Uma condicional no início mapeia: [x if x > 0 else 0 for x in data] mantém todos os itens e reescreve os que falham no teste (aqui, zerando os negativos). Você pode combinar as duas coisas em uma única comprehension, [x * 2 for x in data if x > 0], e encadear várias cláusulas if no final, que se combinam como and.

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]
JunoFiltrando com uma condição Adicione 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.
JunoFiltrando com uma condição Um if no final filtra, mantendo apenas os itens em que a condição é truthy. Isso é diferente de uma condicional dentro da expressão: [x if x > 0 else 0 for x in data] reescreve valores em vez de descartá-los. Saiba qual das duas coisas você quer antes de escrever.
JunoFiltrando com uma condiçãoif no final filtra e muda o tamanho; uma condicional no início mapeia e mantém o tamanho. Confunda as duas e você entrega um resultado com o tamanho errado. Cláusulas if encadeadas no final se combinam com and.

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.

Comprehensions aninhadas são executadas da esquerda para a direita. A primeira cláusula for é o loop externo, a segunda é o interno. Elas produzem um resultado plano único, não uma estrutura 2D. Se a comprehension for difícil de entender rapidamente, escreva os loops explicitamente.

A ordem das cláusulas segue a mesma ordem dos loops aninhados equivalentes: o primeiro for é o loop externo, o segundo é o interno, e uma cláusula posterior pode usar um nome vinculado por uma anterior. Onde isso realmente vale a pena é ao achatar estruturas; onde isso incomoda é em uma grade verdadeira de combinações (todo par entre duas sequências), caso em que itertools.product se lê muito mais claro do que um for duplo. A regra que mantém uma base de código sensata: se interpretar a comprehension leva mais de um segundo, o loop explícito é a documentação melhor, então escreva ele em vez disso.

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 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.

JunoComprehensions aninhadas Duas cláusulas 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.
JunoComprehensions aninhadas Empilhe duas cláusulas for e a primeira é o loop externo, a segunda o interno: [item for row in matrix for item in row] achata uma lista 2D em uma só. O resultado é plano, não uma grade. Quando parar de ser legível de cara, os loops explícitos são melhores.
JunoComprehensions aninhadas O primeiro for é o loop externo, e uma cláusula posterior pode usar um nome de uma anterior. Ótimo para achatar, ruim para combinações de todo par, onde itertools.product se lê mais limpo. Se interpretar leva mais de um segundo, o loop é a documentação melhor.

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.

Dict comprehensions criam um dict novo a partir de qualquer iterável que produza pares chave-valor. A sintaxe é {expr_chave: expr_valor for item in iterável if condição}. Chaves duplicadas geradas pelo loop ficam com o último valor, silenciosamente. .items() em um dict existente é a fonte mais comum de iterável para dict comprehensions.

Duas coisas costumam pegar no código real. Primeiro, toda chave precisa ser hashable (um valor que o Python consegue reduzir a um número fixo de busca, o que significa que ele nunca muda; strings, números e tuplas se qualificam, mas listas e dicts não). Uma expressão de chave que resulta em uma lista levanta TypeError. Segundo, chaves duplicadas não geram erro: se o loop produz a mesma chave duas vezes, o valor mais recente sobrescreve silenciosamente o anterior, o que é uma fonte discreta de perda de dados quando você constrói um dict a partir de uma fonte que na verdade não é única. Quando você só quer combinar dois dicts existentes, o operador | (a forma de merge de dicts, a partir do Python 3.9) expressa a intenção com mais clareza do que uma comprehension.

python
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:

python
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"alice": 87, "carol": 92}
python
words = ["apple", "banana", "cherry"]
word_lens = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}
JunoDict comprehensions{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.
JunoDict comprehensions{chave: valor for item in iterável} cria um dict a partir de qualquer fonte de pares. As duas fontes mais comuns no dia a dia são .items() em um dict existente e zip() em duas listas. Cuidado com chaves duplicadas: a última vence, silenciosamente.
JunoDict comprehensions Chaves precisam ser hashable (nenhuma lista), e chaves duplicadas não geram erro, o valor mais recente sobrescreve silenciosamente, o que consome dados quando sua fonte não é única. Então verifique a unicidade antes de confiar na contagem. Para combinar dois dicts, o | expressa isso de forma mais clara do que uma comprehension.

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.

Set comprehensions usam {expressão for item in iterável} e produzem um set. Elas removem duplicatas automaticamente. Use-as quando precisar de uma coleção única construída a partir de uma transformação, em que a ordem não importa.

O resultado é um set, então duas coisas decorrem disso que a versão em lista não te dá. Valores duplicados vindos da expressão colapsam em um só, automaticamente, e esse é justamente o ponto: é a forma mais limpa de eliminar duplicatas de uma transformação em uma única expressão. O custo é que um set não tem ordem, então você não pode contar com os itens saindo em nenhuma sequência específica. Os membros também precisam ser hashable, a mesma regra que se aplica a chaves de dict: um valor que o Python consegue reduzir a um número fixo de busca, o que exclui listas.

python
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.

JunoSet comprehensions{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.
JunoSet comprehensions{expr for item in iterável} produz um set, removendo duplicatas ao longo do caminho. Use quando quiser uma coleção única a partir de uma transformação e a ordem não importar. Chaves sem dois-pontos, isso é a única coisa que a separa de uma dict comprehension.
JunoSet comprehensions O set descarta duplicatas automaticamente, e esse é o motivo inteiro de escolhê-lo em vez de uma list comprehension. A troca é não ter ordem para contar, e os membros precisam ser hashable, então nenhuma lista dentro. Melhor uso: eliminar duplicatas de uma transformação em uma única expressão.

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.

Uma generator expression produz um iterador, não uma coleção. Ela calcula valores de forma lazy: 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(), já que não há motivo para construir a lista completa antes.

Um generator produz valores de forma lazy (um por vez, apenas quando algo pede o próximo), então seu consumo de memória permanece constante, não importa o tamanho da entrada: ele nunca mantém mais do que o valor atual, enquanto uma list comprehension mantém todos os elementos de uma vez. Isso o torna a escolha certa para uma fonte grande ou em streaming alimentando diretamente sum(), max() ou any(). O modo de falha que vale lembrar é que um generator é de uso único: uma vez que algo o percorre até o fim, ele fica esgotado e não gera mais nada, então um segundo loop sobre o mesmo generator executa zero vezes e não dá erro nenhum. Se você precisa percorrer os dados duas vezes, construa uma lista uma vez e itere sobre ela.

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

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

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

Para 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.

JunoGenerator expressions Um generator parece uma list comprehension com parênteses em vez de colchetes, mas ele gera valores um por um em vez de construir a lista inteira de antemão. Para uma sequência gigante, isso economiza um monte de memória. O caso elegante: passe um direto para sum() ou max() e pule construir a lista de vez.
JunoGenerator expressions Uma generator expression retorna um iterador lazy: ela calcula o próximo valor só quando pedido, então nada constrói a lista completa na memória. Ideal quando o resultado alimenta direto sum(), max() ou any(). Lembre-se: é de uso único, uma vez consumido fica vazio.
JunoGenerator expressions Lazy e com memória constante, não importa o tamanho da entrada, então é a ferramenta certa para uma fonte grande ou em streaming que alimenta sum() ou any(). A pegadinha: é de uso único, e um segundo loop sobre um generator esgotado executa zero vezes sem erro. Precisa de duas passagens? Construa uma lista uma 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.

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

zip() percorre suas entradas em paralelo e para no momento em que a mais curta se esgota. Esse truncamento é a pegadinha a se planejar: combine uma lista de 1000 registros com uma lista de 999 e você perde o último registro silenciosamente, sem erro, sem aviso. Quando os tamanhos deveriam corresponder, zip(seq_a, seq_b, strict=True) (a partir do Python 3.10) levanta uma exceção se eles forem diferentes, em vez de descartar dados, e itertools.zip_longest preenche as lacunas quando os tamanhos podem legitimamente diferir. O outro truque que vale saber: zip(*rows) transpõe, transformando uma lista de linhas em uma lista de colunas, porque * desempacota a lista externa em argumentos separados.

python
names = ["Alice", "Bob", "Carol"]
scores = [87, 74, 92]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Alice: 87
# Bob: 74
# Carol: 92

zip() 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):

python
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:

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})")
Junozip()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.
Junozip()zip() retorna um iterador lazy de tuplas, percorrendo suas entradas em paralelo. Ele para na mais curta, então sequências mais longas são truncadas sem aviso nenhum. itertools.zip_longest() preenche as lacunas quando os tamanhos legitimamente diferem, e zip(*pairs) desfaz o zip.
Junozip() Lazy, paralelo, e para na entrada mais curta, o que descarta dados silenciosamente quando os tamanhos deveriam coincidir. Passe strict=True para fazer uma incompatibilidade levantar erro em vez disso. zip(*rows) é sua transposição, colunas a partir de linhas em uma única chamada.

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.

map(func, iterável) retorna um iterador lazy que aplica func a cada item. filter(func, iterável) retorna um iterador lazy dos itens para os quais func é truthy. Ambos são anteriores às comprehensions. Prefira comprehensions em código novo; use map() quando você já tiver uma função nomeada que faz o que precisa.

Ambos retornam iteradores lazy, não listas, então você os envolve em list() quando quer os valores agora. Eles correspondem um a um às comprehensions que você já conhece: map(f, it) é (f(x) for x in it), e filter(pred, it) é (x for x in it if pred(x)). Essa equivalência é a regra de decisão. Com uma lambda inline, a comprehension se lê melhor e é o padrão moderno. Com uma função nomeada que já faz o trabalho, list(map(int, strings)) se lê como "aplique int sobre strings" e é a escolha mais limpa, então a única decisão real é se você já tem uma função disponível.

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:

python
strings = ["1", "2", "3"]
numbers = list(map(int, strings))   # [1, 2, 3] (mais limpo do que uma comprehension aqui)
Junomap() e filter()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.
Junomap() e filter()map() transforma cada item, filter() mantém os truthy, ambos lazy, então envolva em list() para ver os resultados. Comprehensions são o padrão para código novo. O único lugar em que map() ganha é com uma função nomeada que você já tem: map(int, strings) se lê mais limpo do que a comprehension.
Junomap() e filter()map(f, it) é (f(x) for x in it) e filter(pred, it) é (x for x in it if pred(x)), ambos lazy. Então a escolha é puramente de legibilidade: com uma lambda inline, a comprehension ganha; com uma função já nomeada, map(int, strings) ganha.

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:

python
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}")

Filtre uma lista de usuários para administradores ativos, construa um dict de busca de id para nome, e reúna nomes ordenados em uma única passagem cada:

python
raw_users = [
    {"id": 1, "name": "Alice", "role": "admin", "active": True},
    {"id": 2, "name": "Bob", "role": "user", "active": False},
    {"id": 3, "name": "Carol", "role": "admin", "active": True},
    {"id": 4, "name": "Dave", "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 dict comprehension, ordene com uma lambda, e normalize os valores em uma segunda comprehension:

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 dict comprehension constrói o mapeamento em uma única expressão. A lambda de ordenação evita uma função de chave nomeada. A comprehension de normalização transforma os valores sem modificar o dict original.