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

Lambdas e compreensões

docs.scrimba.com

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.

Lambdas, compreensões 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 código Python e valem a pena reconhecer e escrever com fluência. O princípio norteador: use-as quando esclarecam a intenção, não apenas para encurtar.

Essas ferramentas todas expressam uma transformação como uma expressão (um trecho de código que se avalia a um valor) em vez de um loop imperativo que constrói um resultado passo a passo. Lambdas lhe dão uma pequena função sem nome inline. Compreensões constroem uma coleção em uma passada. Geradores são lazy (produzem valores sob demanda em vez de tudo de uma vez), então mantêm um item na memória em vez de toda a sequência. zip percorre várias sequências em sincronia. O ganho é código que se lê como "o que quero" em vez de "como montar", desde que você pare antes que a linha única deixe de ser legível.

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

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 declaração. Seu uso principal é como argumento inline key= ou callback onde um def completo adicionaria indireção desnecessária. Para qualquer coisa mais complexa, use def.

Uma lambda é o mesmo tipo de objeto função que def produz, com três limitações: não tem nome (aparece como <lambda> em um traceback, o relatório de erro que Python imprime quando algo levanta), seu corpo é uma expressão então não pode conter declarações, e não leva docstring. A armadilha que custa tempo real de debug é late binding em um closure (uma função que captura uma variável do escopo ao seu redor). Uma lambda construída dentro de um loop captura a variável do loop por referência, então cada lambda vê o valor final da variável, não o valor que tinha quando a lambda foi criada:

python
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2], 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 vincula o valor agora

O truque i=i fixa o valor atual como um argumento padrão no momento da criação. Recorra a ele sempre que construir callables em um loop.

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

Isto é equivalente a:

python
def double(x):
    return x * 2

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

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

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

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.

Compreensões de lista são uma substituição concisa do padrão build-with-a-loop. Geralmente são mais rápidas que o loop for equivalente com .append(), já que Python não chama um método em cada passagem. A estrutura é [expression for item in iterable if condition]; a cláusula if é opcional.

Uma compreensão é significativamente mais rápida que o mesmo loop chamando .append() em cada passagem, então é a forma idiomática de construir uma lista a partir de uma transformação. Também é executada em seu próprio scope (a região onde um nome é visível), o que significa que a variável do loop não vaza para o código circundante: depois de [n ** 2 for n in numbers], o nome n não existe fora da compreensão. A armadilha a observar é recorrer a uma onde o corpo faz trabalho real. Uma vez que a expressão cresce além de uma transformação limpa, ou você gostaria de adicionar logging ou um bloco try, o loop explícito é a ferramenta melhor, porque uma compreensão só pode conter uma expressão, nunca uma declaração.

O caminho 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: [expression for item in iterable].

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
JunoCompreensões de lista[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.
JunoCompreensões de lista[expression for item in iterable] substitui o padrão build-with-a-loop-and-.append(), e lê mais limpo fazendo. Produz uma nova lista e não muta nada. Mantenha a expressão simples, ou a legibilidade que ganhou se foi.
JunoCompreensões de lista Mais rápida que o loop .append() e é executada em seu próprio scope, então a variável do loop nunca vaza. A linha a manter: uma compreensão pega uma expressão, não uma declaração, então no segundo em que você quer um bloco try ou uma linha de log, o loop explícito vence.

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.

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

A posição do if decide se você filtra ou transforma, e misturá-los é uma causa comum de um resultado com comprimento errado. Um if no final filtra: [x for x in data if x > 0] descarta itens. Um condicional na frente mapeia: [x if x > 0 else 0 for x in data] mantém cada item e reescreve os que falharam (aqui, fixando negativos a zero). Você pode combinar ambos em uma compreensão, [x * 2 for x in data if x > 0], e empilhar múltiplas cláusulas if à direita, que se encadeiam 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]
JunoFiltragem com uma condição Adicione 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.
JunoFiltragem com uma condição Um if à direita filtra, mantendo apenas itens onde a condição é truthy. Isso é diferente de um condicional dentro da expressão: [x if x > 0 else 0 for x in data] reescreve valores em vez de descartá-los. Saiba qual você quer antes de escrever.
JunoFiltragem com uma condiçãoif ao final filtra e muda o comprimento; um condicional na frente mapeia e mantém. Confunda os dois e você entrega um resultado que é do tamanho errado. Cláusulas if à direita empilhadas se encadeiam com and.

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.

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

A ordem das cláusulas se lê na mesma ordem que os 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 compensa é na achatação; onde dói é uma grade verdadeira de combinações (cada par de duas sequências), para a qual itertools.product lê muito mais claro que um for duplo. A regra que mantém uma base de código sã: se parsear a compreensão leva mais de um segundo, o loop explícito é a melhor documentação, então escreva-o 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 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.

JunoCompreensões aninhadas Duas cláusulas 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.
JunoCompreensões 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. O resultado é plano, não uma grade. Quando deixa de ser legível à primeira vista, os loops explícitos são melhores.
JunoCompreensões aninhadas O primeiro for é o loop externo, e uma cláusula posterior pode usar um nome de uma anterior. Ótimo para achatação, fraco para combinações de cada par, onde itertools.product lê mais limpo. Se parsear leva mais de um segundo, o loop é a melhor documentação.

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.

Compreensões de dicionário criam um novo dict de qualquer iterável produzindo pares chave-valor. A sintaxe é {key_expr: val_expr for item in iterable if condition}. Chaves duplicadas do loop usam o último valor, silenciosamente. .items() em um dict existente é a fonte iterável mais comum para compreensões de dicionário.

Duas coisas mordem em código real. Primeiro, cada chave tem que ser hashable (um valor que Python pode reduzir a um número de lookup fixo, o que significa que nunca muda, então strings e números e tuplas se qualificam mas listas e dicts não). Uma expressão de chave que rende uma lista levanta TypeError. Segundo, chaves duplicadas não causam erro: se o loop produz a mesma chave duas vezes, o valor posterior silenciosamente sobrescreve o anterior, que é uma fonte silenciosa de dados descartados quando você constrói um dict de uma fonte que não é realmente única. Quando você só quer mesclar dois dicts existentes, o operador | (a forma de mesclagem de dict, Python 3.9 e acima) declara a intenção mais claramente 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}
JunoCompreensões de dicionário{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.
JunoCompreensões de dicionário{key: value for item in iterable} cria um dict de qualquer fonte de pares. As duas fontes de uso diário são .items() em um dict existente e zip() sobre duas listas. Observe chaves duplicadas: a última vence, silenciosamente.
JunoCompreensões de dicionário Chaves devem ser hashable (sem listas), e chaves duplicadas não causam erro, o valor posterior silenciosamente sobrescreve, o que come dados quando sua fonte não é única. Então verifique unicidade antes de confiar na contagem. Para mesclar dois dicts, | diz isso mais claramente que uma compreensão.

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.

Compreensões de conjunto usam {expression for item in iterable} e produzem um set. Elas deduplicam automaticamente. Use-as quando você precisa de uma coleção única construída a partir de uma transformação, onde a ordem não importa.

O resultado é um set, então duas coisas seguem que a versão de lista não lhe dá. Valores duplicados da expressão colapsam em um automaticamente, que é o ponto: é a forma mais limpa de desduplicar uma transformação em uma única expressão. O custo é que um conjunto não tem ordem, então você não pode contar com os itens saindo em qualquer sequência particular. Os membros também têm que ser hashable, a mesma regra que se aplica a chaves de dict: um valor que Python pode reduzir a um número de lookup fixo, o que descarta listas.

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

JunoCompreensões de conjunto{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.
JunoCompreensões de conjunto{expr for item in iterable} produz um set, deduplicando conforme vai. Recorra a ele quando você quer uma coleção única de uma transformação e ordem não importa. Chaves com sem dois-pontos, isso é a única coisa separando de uma compreensão de dicionário.
JunoCompreensões de conjunto O conjunto descarta duplicatas automaticamente, que é a razão inteira de escolhê-lo sobre uma compreensão de lista. O tradeoff é nenhuma ordem em que confiar, e membros devem ser hashable, então nenhuma lista dentro. Melhor ajuste: desduplicar uma transformação em uma expressão.

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.

Uma expressão geradora produz um iterador, não uma coleção. Ela computa valores lazily: o próximo valor é apenas produzido quando solicitado. Isso é mais valioso quando o resultado é consumido imediatamente por uma função como sum(), max() ou any(), então não há ponto em construir a lista completa primeiro.

Um gerador produz valores lazily (um de cada vez, apenas quando algo pede pelo próximo), então sua memória fica plana não importa o tamanho da entrada: nunca mantém mais do que o valor atual, onde uma compreensão de lista mantém cada elemento de uma vez. Isso o torna a chamada correta para uma fonte grande ou em streaming alimentando direto em sum(), max() ou any(). O modo de falha a lembrar é que um gerador é de uso único: uma vez que algo o itera até o fim, ele é exhausted e rende nada mais, então um segundo loop sobre o mesmo gerador roda zero vezes e não dá erro. Se você precisa percorrer os dados duas vezes, construa uma lista uma vez e iterate aquela.

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

Quando passando um gerador direto para uma função como sum(), max(), min() ou any(), você pode descartar os parênteses extra:

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

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

JunoExpressões geradoras Um gerador parece uma compreensão de lista com parênteses em vez de colchetes, mas faz valores um de cada vez em vez de construir a lista inteira na frente. Para uma sequência gigante isso economiza uma pilha de memória. O caso elegante: coloque um direto em sum() ou max() e pule construir a lista inteiramente.
JunoExpressões geradoras Uma expressão geradora retorna um iterador lazy: computa o próximo valor apenas quando perguntado, então nada constrói a lista completa na memória. Melhor quando o resultado alimenta direto em sum(), max() ou any(). Lembre-se que é de uso único, uma vez consumido está vazio.
JunoExpressões geradoras Lazy e plana na memória porém grande a entrada, então é a ferramenta certa para uma fonte grande ou em streaming alimentando sum() ou any(). A pegadinha: é de uso único, e um segundo loop sobre um gerador exhausted roda zero vezes sem erro. Precisa de duas passadas? Construa uma lista uma vez.

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.

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

zip() percorre suas entradas em sincronia e para o momento em que a mais curta acaba. Esse truncamento é a pegadinha a design around: pareie uma lista de 1000 registros com uma lista de 999 e você silenciosamente perde o último registro, sem erro, sem aviso. Quando os comprimentos devem corresponder, zip(seq_a, seq_b, strict=True) (Python 3.10 e acima) levanta se diferem em vez de descartar dados, e itertools.zip_longest preenche os gaps quando são permitidos diferir. O outro truque que vale a pena 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 = ["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 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):

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

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() 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.
Junozip()zip() retorna um iterador lazy de tuplas, passando pelas suas entradas em paralelo. Para na mais curta, então sequências mais longas ficam truncadas sem um pio. itertools.zip_longest() preenche os gaps quando comprimentos legitimamente diferem, e zip(*pairs) deszipar.
Junozip() Lazy, paralelo, e para na entrada mais curta, o que silenciosamente descarta dados quando comprimentos eram para corresponder. Passe strict=True para fazer um descasamento levantar em vez. zip(*rows) é sua transposição, colunas de linhas em uma chamada.

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.

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

Ambas retornam iteradores lazy, não listas, então você as embrulha em list() quando você quer os valores agora. Elas mapeiam um-para-um em compreensões 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 um lambda inline, a compreensão lê melhor e é o padrão moderno. Com uma função nomeada que já faz o trabalho, list(map(int, strings)) lê como "mapear int sobre strings" e é a escolha mais limpa, então a única chamada real é se você já tem uma função em mãos.

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:

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

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:

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 para admins ativos, construa um lookup de id-para-name dict, e colha nomes classificados em uma 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}")

Pareie nomes de features com pontuações de importância usando zip, construa uma compreensão de dict, 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}")

# Normalize to sum to 1.0 (values already sum to 1 here, but shown as a pattern)
total = sum(feat_dict.values())
normalised = {k: round(v / total, 4) for k, v in feat_dict.items()}
print(f"Normalised: {normalised}")

zip pareia as duas listas sem construir tuplas intermediárias. A compreensão de dict constrói o mapeamento em uma expressão. A lambda de sort evita uma função de chave nomeada. A compreensão de normalização transforma valores sem mutar o dict original.