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
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.
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
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.
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
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
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. Esseifnão temelse. - Antes do
for, junto da conta: escolhe entre dois valores. Esseifsempre vem comelse, 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 definedNum 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
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 sequenceO 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 iterableO 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 syntaxO 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.