Pular para o conteúdo

Fundamentos

Operações com listas em Python: busca, filtragem e compreensões

Aprenda a buscar, filtrar e transformar listas em Python: laço com append, a compreensão de lista, any e all, sort e sorted, com exemplos de ESP32.

Por Prof. Rodrigo Faustino · Atualizado em · 14 min de leitura

Para buscar, filtrar ou transformar os itens de uma lista, o caminho clássico é um for com um if e um append. A compreensão de lista é uma forma mais curta de escrever o mesmo: uma única linha entre colchetes, com a conta, o laço e a condição. Quem domina as duas formas escolhe a mais legível para cada caso, e tem à mão as funções prontas any, all, sum, max e sorted para os casos mais comuns.

O artigo anterior mostrou como criar, ler, alterar e percorrer listas. Este parte daí e responde às perguntas que se faz a uma lista depois que ela existe: onde está este valor, quais itens passam num teste, o que acontece se cada item for convertido. Veremos primeiro a busca, depois o filtro e a transformação com laço, em seguida a compreensão de lista, a ordenação, os erros mais comuns e um exemplo com leituras de sensor.

Três operações, uma decisão

Três quadros lado a lado. Buscar: na lista 12, 45, 7, 89 a pergunta é se o 7 está na lista e a resposta é uma só, a posição 2, ou menos 1 se não existir. Filtrar: da lista 3, 8, 5, 12 sobram só os pares 8 e 12, com menos itens que a original. Transformar: da lista 3, 8, 5 sai a lista 6, 16, 10, com o mesmo número de itens. Uma faixa pede para decidir qual das três operações o problema exige.
Buscar devolve uma resposta, filtrar devolve menos itens e transformar devolve o mesmo número de itens.

Quase tudo que se faz com uma lista cai em uma de três operações, e identificar qual delas o problema pede é metade da solução.

A busca faz uma pergunta e devolve uma resposta: o valor está na lista, em que posição, qual é o maior, quantos passam no teste. O resultado não é uma lista.

O filtro devolve uma lista nova com só os itens que passam num teste. A lista nova tem o mesmo número de itens ou menos.

A transformação devolve uma lista nova em que cada item foi trocado por uma conta com ele, e por isso tem exatamente o mesmo tamanho.

Filtro e transformação têm uma coisa em comum que será importante adiante: nenhum dos dois altera a lista original. Eles montam outra.

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

Buscar um valor

A pergunta mais simples é se o valor existe, e o in responde. Para saber onde, o método index devolve a posição da primeira ocorrência. O problema do index é que ele dá erro quando o valor não está na lista. Escrever a busca à mão resolve isso, e é um exercício clássico:

nomes = ["Ana", "Bia", "Caio", "Duda"]
print("Caio" in nomes)
print(nomes.index("Caio"))

def buscar(lista, alvo):
    for i in range(len(lista)):
        if lista[i] == alvo:
            return i
    return -1

print(buscar(nomes, "Bia"))
print(buscar(nomes, "Zeca"))
alvo = "Duda"
pos = buscar(nomes, alvo)
if pos == -1:
    print(alvo, "não encontrado")
else:
    print(alvo, "está na posição", pos)
print("fim")

A saída é True, 2, 1, -1, Duda está na posição 3 e fim. A função buscar percorre as posições e, no primeiro item igual ao alvo, devolve o índice e encerra. Se o laço termina sem achar, a última linha devolve -1. O -1 funciona como sinal de "não achei" porque a busca nunca devolve essa posição, e quem chama a função testa o resultado antes de usá-lo. Esse return no meio do laço é o que o artigo sobre return chamou de sair da função cedo, e o desafio Busca em Vetor pede uma busca desse tipo.

Há perguntas parecidas com respostas diferentes. Em vez da primeira posição, quer-se todas. Ou quer-se o primeiro item que passa num teste, e não um valor exato:

leituras = [510, 520, 505, 520, 530, 520]
posicoes = []
for i in range(len(leituras)):
    if leituras[i] == 520:
        posicoes.append(i)
print(posicoes)
primeiro = None
for x in leituras:
    if x > 525:
        primeiro = x
        break
print(primeiro)
print("fim")

A saída é [1, 3, 5], 530 e fim. Na primeira busca o laço não para: a lista posicoes começa vazia e recebe cada índice em que o valor aparece. Na segunda, o break interrompe o laço assim que o primeiro item acima de 525 é achado, e a variável começa com None para que, se nenhum passar no teste, o resultado seja um valor que o programa possa testar com if primeiro is None. O break foi tratado no artigo sobre break, continue e else nos laços.

Maior, menor e a posição deles

A função max resolve o maior valor, mas o desafio Maior da Lista pede o laço, e há um motivo: o laço permite guardar também a posição, e max sozinho não dá isso.

valores = [12, 45, 7, 89, 23]
maior = valores[0]
pos = 0
for i in range(1, len(valores)):
    if valores[i] > maior:
        maior = valores[i]
        pos = i
print(maior, pos)
negativos = [-5, -2, -9]
errado = 0
for n in negativos:
    if n > errado:
        errado = n
print(errado)
certo = negativos[0]
for n in negativos:
    if n > certo:
        certo = n
print(certo)
print("fim")

A saída é 89 3, 0, -2 e fim. A primeira parte guarda o maior e sua posição, e começa o laço em 1 porque o item 0 já foi usado como ponto de partida. A segunda parte mostra a armadilha: quem começa o maior em 0 acha que a resposta para [-5, -2, -9] é 0, um valor que não está na lista. O certo é partir do primeiro item, como na terceira parte. O mesmo vale para o menor.

Contar e perguntar a todos

Contar quantos itens passam num teste é um acumulador, como os do artigo anterior. As funções any e all respondem a duas perguntas muito comuns: "algum item passa?" e "todos os itens passam?".

notas = [8, 4, 7, 9, 3]
aprovados = 0
for n in notas:
    if n >= 6:
        aprovados += 1
print(aprovados)
print(any([n < 4 for n in notas]))
print(all([n >= 3 for n in notas]))
print(any(n < 4 for n in notas))
print(all(n >= 6 for n in notas))
print("fim")

A saída é 3, True, True, True, False e fim. O any devolve True se pelo menos um item for verdadeiro, e o all, se todos forem. Aqui eles recebem uma lista de verdadeiros e falsos montada por uma compreensão, que ainda não foi explicada, mas que dá para ler: "para cada nota, nota < 4". As duas últimas linhas fazem a mesma pergunta sem os colchetes, e a forma é explicada mais abaixo.

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

Filtrar e transformar com laço

O filtro e a transformação seguem o mesmo molde. Cria-se uma lista vazia, percorre-se a original e, para cada item, decide-se o que acrescentar:

numeros = [3, 8, 5, 12, 7, 20]
pares = []
for n in numeros:
    if n % 2 == 0:
        pares.append(n)
print(pares)
dobros = []
for n in numeros:
    dobros.append(n * 2)
print(dobros)
print(numeros)
print("fim")

A saída é [8, 12, 20], [6, 16, 10, 24, 14, 40], [3, 8, 5, 12, 7, 20] e fim. O filtro tem o if antes do append, e a transformação não tem if, e põe a conta dentro do append. A última linha confirma que a lista original ficou como estava. Esse é o jeito seguro de trabalhar, e o contrário, mexer na original durante o for, foi o erro mostrado no artigo anterior.

O desafio Soma dos Pares mistura as duas ideias: filtra por n % 2 == 0 e acumula a soma em vez de guardar os itens.

Compreensão de lista

No alto, o laço de quatro linhas que cria a lista pares vazia, percorre numeros, testa se n é par e faz append. Embaixo, a mesma conta numa linha só, pares igual a n for n in numeros if n resto 2 igual a 0, entre colchetes. Três chaves coloridas marcam as partes: o que entra na lista, de onde vem, que é o for, e o filtro opcional, que é o if.
A compreensão de lista condensa as quatro ideias do laço em uma única linha.

A compreensão de lista (em inglês, list comprehension) é a forma curta desses laços. Ela é escrita entre colchetes e tem três partes: o que entra na lista, o for que diz de onde vêm os itens e, se for preciso, um if que filtra. A figura mostra que as quatro linhas do laço de filtro viram uma.

numeros = [3, 8, 5, 12, 7, 20]
dobros = [n * 2 for n in numeros]
pares = [n for n in numeros if n % 2 == 0]
pares_dobrados = [n * 2 for n in numeros if n % 2 == 0]
rotulos = ["par" if n % 2 == 0 else "impar" for n in numeros]
quadrados = [i * i for i in range(1, 6)]
letras = [c for c in "casa"]
print(dobros)
print(pares)
print(pares_dobrados)
print(rotulos)
print(quadrados)
print(letras)
print(numeros)
print("fim")

A saída é [6, 16, 10, 24, 14, 40], [8, 12, 20], [16, 24, 40], ['impar', 'par', 'impar', 'par', 'impar', 'par'], [1, 4, 9, 16, 25], ['c', 'a', 's', 'a'], a lista original intacta e fim.

Cada linha usa uma variação. dobros só transforma, sem if. pares só filtra: a parte "o que entra" é o próprio n. pares_dobrados faz os dois, filtra e transforma. Em rotulos aparece um if-else no começo, antes do for, que escolhe qual valor entra: é uma decisão por item, e todos os itens entram. Em quadrados, o for percorre um range em vez de uma lista, e em letras percorre um texto, porque o for aceita qualquer sequência.

A forma de ler é na ordem do laço, e não da esquerda para a direita como uma frase: "para cada n em numeros, se for par, guarde n". A parte da frente só diz o que guardar.

Onde fica o if

O if aparece em dois lugares diferentes, e cada lugar tem um papel:

  • Depois do for: filtra. Os itens que não passam ficam de fora. Esse if não tem else.
  • Antes do for, junto da conta: escolhe entre dois valores. Esse if sempre vem com else, porque todos os itens entram e precisam de um valor.

Misturar os dois é a confusão mais comum. Se você escrever [n * 2 for n in numeros if n > 1 else 0], o resultado é um erro de sintaxe, que está na seção de erros. A correção é decidir o que se quer: filtrar (if no fim, sem else) ou escolher o valor (if-else no começo).

O nome do laço não vaza

Outra diferença para o laço comum: a variável do for dentro da compreensão existe só ali. Depois da linha, ela deixa de existir:

numeros = [1, 2, 3]
dobros = [n * 2 for n in numeros]
print(dobros)
print(n)
[2, 4, 6]
NameError: name 'n' isn't defined

Num for comum, o n continuaria valendo o último item depois do laço. Na compreensão, não. Isso evita vazamentos de nome, mas surpreende quem esperava ler o n depois.

Compreensão com sum, len, max e any

A compreensão se encaixa bem dentro de outras funções, porque devolve uma lista que elas aceitam:

numeros = [3, 8, 5, 12, 7, 20]
print(len([n for n in numeros if n > 5]))
print(sum([n for n in numeros if n > 5]))
print(sum(n for n in numeros if n > 5))
print(max([n * 2 for n in numeros]))
print([a + b for a, b in zip([1, 2, 3], [10, 20, 30])])
print([x for x in numeros if x > 100])
print("fim")

A saída é 4, 47, 47, 40, [11, 22, 33], [] e fim. As duas primeiras linhas contam e somam os itens maiores que 5. Na terceira, os colchetes sumiram, e o resultado é o mesmo: sem eles, o Python não cria a lista inteira e entrega os itens um a um para o sum, com o mesmo resultado. Em any e all, essa forma sem colchetes é a mais comum. O zip, do artigo anterior, junta duas listas posição a posição, e a compreensão soma cada par. A última linha mostra que, se nenhum item passa no filtro, o resultado é a lista vazia, e não um erro.

Quando preferir o laço

A compreensão não é melhor sempre. Ela cabe quando a conta é curta e o resultado é uma lista. O laço continua sendo a escolha quando:

  • há mais de uma coisa a fazer por item, como imprimir e acumular ao mesmo tempo;
  • é preciso um break, que a compreensão não tem;
  • a linha ficaria tão longa que exigiria uma releitura para entender.

Uma regra prática de ensino: se você precisa parar para decifrar a compreensão, escreva o laço. Quem lê o programa depois agradece.

Ordenar uma lista

Ordenar também é uma operação de lista, e há duas formas com uma diferença que costuma custar caro:

lista = [3, 1, 2]
r = lista.sort()
print(r, lista)
original = [3, 1, 2]
nova = sorted(original)
print(original, nova)
lista.reverse()
print(lista)
print(sorted([3, 1, 2], reverse=True))
palavras = ["pera", "uva", "banana"]
print(sorted(palavras, key=len))
palavras.sort(key=len)
print(palavras)
print("fim")

A saída é None [1, 2, 3], [3, 1, 2] [1, 2, 3], [3, 2, 1], [3, 2, 1], ['uva', 'pera', 'banana'], ['uva', 'pera', 'banana'] e fim.

O método lista.sort() ordena a própria lista e devolve None, como o append do artigo anterior. A função sorted(lista) devolve uma lista nova, ordenada, e deixa a original como estava. O reverse() inverte no lugar, e sorted(..., reverse=True) entrega a lista nova em ordem decrescente. O parâmetro key, que o artigo sobre lambda, map, filter e sorted explicou, também funciona nas duas formas. Como o assunto de ordenar à mão volta nos artigos sobre algoritmos, aqui basta saber usar as duas.

Remover repetidos e remover todas as ocorrências

O artigo anterior deixou duas perguntas no ar. A primeira: como tirar os valores repetidos de uma lista mantendo a ordem? A segunda: como remover todas as ocorrências de um valor, já que o remove só tira a primeira?

leituras = [5, 3, 5, 2, 3, 9]
unicos = []
for x in leituras:
    if x not in unicos:
        unicos.append(x)
print(unicos)
d = [1, 2, 3, 2, 2]
print([x for x in d if x != 2])
print(d)
print("fim")

A saída é [5, 3, 2, 9], [1, 3], [1, 2, 3, 2, 2] e fim. Na primeira parte, o not in pergunta à lista nova se o item já entrou, e só acrescenta os que ainda não estão, o que mantém a ordem da primeira aparição. Na segunda, a compreensão monta uma lista sem o valor 2. A original, d, não mudou, e por isso o resultado costuma ser guardado: d = [x for x in d if x != 2]. Essa forma de "remover" criando uma lista nova é a recomendada, e evita o problema de apagar itens durante o laço.

Na ESP32: filtrar leituras antes de calcular

Uma linha de sete leituras do sensor: 512, 0, 530, 4095, 498, 0 e 505, com índices de 0 a 6. Os valores 0 e 4095 estão em vermelho, como leituras inválidas. Uma compreensão com a condição 0 menor que x menor que 4095 produz a lista 512, 530, 498, 505, e a média inteira dessas quatro leituras é 511. Embaixo, outra compreensão devolve as posições acima de 520, que são 2 e 3.
Filtrar as leituras inválidas antes de calcular a média mantém o resultado confiável.

Um sensor ligado ao conversor analógico da ESP32 pode dar, de vez em quando, um 0 ou um 4095, os dois extremos da escala, que muitas vezes indicam sensor sem sinal ou saturado e não uma medida de verdade. A média simples dessas leituras sai errada, e o filtro resolve:

leituras = [512, 0, 530, 4095, 498, 0, 505]
validas = [x for x in leituras if 0 < x < 4095]
print(validas)
print(sum(validas) // len(validas))
altas = [i for i in range(len(leituras)) if leituras[i] > 520]
print(altas)
print("fim")

A saída é [512, 530, 498, 505], 511, [2, 3] e fim. A condição 0 < x < 4095 é uma comparação encadeada, que testa as duas pontas de uma vez. Depois do filtro, a média usa só as quatro leituras válidas. A última compreensão percorre as posições, e não os valores, para devolver onde as leituras altas estão, e esse é o padrão para quando se quer o índice dos itens que passam num teste. Os valores 0 e 4095 são os extremos de uma leitura de 12 bits, e o artigo sobre ADC na ESP32 explica a faixa. Numa placa de verdade, leituras viria do adc.read() em um laço com sleep, e o programa teria um limite para o tamanho da lista, como a janela do artigo anterior.

Erros comuns

As mensagens abaixo são as do simulador, que imita o MicroPython. O tipo do erro é o mesmo no Python de computador, e o texto pode mudar.

print(max([]))
ValueError: arg is an empty sequence

O max e o min não têm resposta para uma lista vazia. Quando a lista pode estar vazia, o if lista: antes evita o erro. O mesmo vale para o min.

print([x for x in 5])
TypeError: 'int' object isn't iterable

O for da compreensão precisa de algo que se possa percorrer. Um número sozinho não é. A causa costuma ser trocar range(5) por 5.

lista = [3, 1, 2]
lista = lista.sort()
print(lista)
print(lista[0])
None
TypeError: 'NoneType' object isn't subscriptable

É o mesmo erro do append: o sort ordena no lugar e devolve None, e a atribuição troca a lista por None. Os dois caminhos corretos são lista.sort() sozinho, ou lista = sorted(lista).

print(sum(["a", "b"]))
TypeError: unsupported types for __add__: 'int', 'str'

O sum começa em 0 e soma números. Textos não entram nessa conta, e a lista de textos se junta com outro método, que fica para o artigo de strings.

print([n * 2 for n in [1, 2, 3] if n > 1 else 0])
SyntaxError: invalid syntax

O if depois do for filtra e não aceita else. Para escolher entre dois valores, o if-else vai no começo: [n * 2 if n > 1 else 0 for n in [1, 2, 3]].

O que costuma aparecer em sala

Os padrões abaixo se repetem em turmas de iniciantes e também com gente que programa há anos.

O primeiro é o lista = lista.sort(), que quase todo mundo escreve uma vez. O erro só aparece na linha seguinte, quando a lista virou None, e a causa está uma linha acima, o que confunde.

O segundo é o maior = 0 para achar o maior valor. Funciona nos testes com números positivos e falha quando a lista só tem negativos, que quase ninguém testou. Vale o mesmo para menor = 0, e o equivalente com um valor "bem grande" escolhido de cabeça, como menor = 999, que deixa de funcionar na primeira leitura de 1000.

O terceiro é usar index sem perguntar antes com in, e depois descobrir o ValueError em produção, quando o valor deixa de estar na lista.

O quarto é a compreensão ilegível. Duas compreensões aninhadas, com if-else no meio e nomes de uma letra, são rápidas de escrever e lentas de depurar. A pergunta de ouro é se, em uma semana, quem ler isso (inclusive você) vai entender de primeira.

O quinto é esperar que a compreensão altere a lista original. Escrever [n * 2 for n in numeros] sozinho, sem atribuir, calcula e joga fora. Sem o numeros = na frente, nada muda.

O sexto é confundir os dois lugares do if, visto acima. O if-else que escolhe o valor tem a forma valor_se_sim if teste else valor_se_nao, e é essa a forma que vai antes do for.

Exercício: análise de números

Escreva um programa que pergunte quantos números serão lidos, leia cada um, guarde numa lista e mostre a lista, a média, os números acima da média e a posição do maior.

quantidade = int(input("quantos numeros: "))
numeros = []
for i in range(quantidade):
    numeros.append(int(input("numero: ")))
media = sum(numeros) / len(numeros)
acima = [n for n in numeros if n > media]
maior = max(numeros)
posicoes = [i for i in range(len(numeros)) if numeros[i] == maior]
print("numeros", numeros)
print("media", media)
print("acima da media", acima)
print("maior", maior, "nas posicoes", posicoes)

Com as entradas 5, 4, 9, 2, 9 e 6, as quatro linhas finais são numeros [4, 9, 2, 9, 6], media 6.0, acima da media [9, 9] e maior 9 nas posicoes [1, 3]. Note que o maior aparece duas vezes e a compreensão devolve as duas posições, o que o index sozinho não faria. Para treinar, mostre também a lista dos números abaixo da média, e depois proteja o programa para o caso de o usuário digitar 0 como quantidade.

Perguntas frequentes

A compreensão de lista é mais rápida que o laço? Não é por velocidade que ela se usa. A razão é a clareza quando a conta é curta.

Posso usar lista.sort() e sorted() no mesmo programa? Pode. A regra é que sort altera a lista e sorted devolve outra, e a escolha depende de você querer manter a original.

Existe compreensão para texto, dicionário e conjunto? Existe uma forma parecida para dicionários e conjuntos, com chaves no lugar dos colchetes, e os artigos sobre esses tipos mostram os detalhes. O que foi aprendido aqui vale como base.

Como filtro uma lista de listas? O mesmo for e if, com cada item sendo uma lista. As listas dentro de listas aparecem no artigo sobre matrizes.

Quando uso any e all em vez de um laço? Quando a pergunta é "algum" ou "todos". O laço com break faz o mesmo e é necessário se houver mais coisas a fazer no meio.

Para continuar

Os desafios Busca em Vetor, Maior da Lista e Soma dos Pares praticam a busca, o maior à mão e o filtro com acumulador. Para rever as bases, os artigos sobre listas e sobre lambda, map, filter e sorted completam este.

O próximo artigo trata das tuplas e do desempacotamento: a lista que não pode ser alterada, por que ela existe e como a, b = b, a troca duas variáveis sem uma terceira.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre