Listas e dicionários são duas estruturas de dados bastante usadas no dia-a-dia. Em resumo, listas servem para guardar sequências ordenadas de informações. Dicionários, por outro lado, permitem atrelar uma chave à um valor.
Uma Nota Sobre Nomenclatura
Compreensão de Listas — é um termo que tem se tornado mais comum em Português do Brasil! Ainda me soa estranho, mas provavelmente isso vem da minha falta de hábito com o termo. É bom ter uma expressão "nossa" para coisas técnicas. Obrigado ao grande @villares pelo toque!
Sequência Ordenada — significa que a lista "lembra" a ordem em que os itens foram inseridos nela e não que esses itens serão magicamente ordenados como por exemplo em ordem alfabética ou algo do tipo. Em Inglês existem dois termos diferentes para isso: "ordered" para coisas como uma lista comum e "sorted" para uma lista que foi ordenada por algum critério como alfabético, crescente, etc
Criando Listas com Comprehensions
Suponha que você queira gerar 1000 números inteiros de 0 a 100 e então você quer separar todos os números que estão acima da média:
from random import randint
# Cria 1000 números de 0 a 100
n = []
for _ in range(1000):
n.append(randint(0, 100))
média = sum(n) / 1000
# Filtra os números
selecionados = []
for i in n:
if i >= média:
selecionados.append(i)
Esse exemplo mostra dois casos úteis para usarmos list comprehensions:
- Criar uma lista com base na repetição de uma chamada de função
- Criar uma lista com base em outra lista
Criando Listas por Repetição de Chamadas
A lista n
contém o resultado de 1000 chamadas para a função randint(0, 100)
. Nesse caso, aquele loop for pode ser reescrito assim:
n = [randint(0, 100) for _ in range(1000)]
Note que a chamada da função foi para o começo, a estrutura do loop foi para depois da chamada da função e o append
desapareceu. Essa é a estrutura geral de uma list comprehension:
lista = [elemento_para_adicionar for variável in iterador]
Você pode usar a variável de iteração no cálculo do elemento que vai ser adicionado também. No meu exemplo eu usei underline porquê eu não precisava de fato do valor [0...1000)
, eu só queria repetir algo mil vezes. Em Python, quando precisamos colocar uma variável em algum lugar mas a gente não se importa com o valor dela de fato, usamos _
como nome.
Criando Listas com Base em Outra
Agora nós já temos nossa lista de mil números aleatórios! A segunda parte do nosso código tinha essa cara:
média = sum(n) / 1000
# Filtra os números
selecionados = []
for i in n:
if i >= média:
selecionados.append(i)
A criação de uma lista com base em uma outra, checando se os itens satisfazem uma condição é um filtro. Podemos implementar essa estrutura também com uma list comprehension:
média = sum(n) / 1000
# Filtra os números
selecionados = [i for i in n if i >= média]
Observe como a estrutura foi modificada de forma muito parecida com a anterior ― a coisa que queremos colocar na lista foi pro começo, logo em seguida nós "embutimos" o loop. A diferença agora é que depois do loop colocamos uma condicional. Essa estrutura pode ser lida como
"Adicionei
emselecionados
ondei
são os elementos den
que possuem valor maior ou igual àmédia
"
Cuidado com Abusos de Notação!
Comprehensions são estruturas bem legais e interessantes em Python. Porém, elas podem ser abusadas. A função principal de uma comprehension é criar uma estrutura com base em outra. Ou seja, você usa uma comprehension quando se interessa pelo resultado dela.
Um abuso possível é usar de comprehensions como substitutas genéricas de loop for. Por exemplo, podemos imprimir todos os números de 0 à 10 assim:
[print(i) for i in range(11)]
A execução desse código será:
0
1
2
3
4
5
6
7
8
9
10
Out[1]: [None, None, None, None, None, None, None, None, None, None, None]
Usar de comprehensions quando na verdade você se importa apenas com um efeito colateral da função invocada e não com a estrutura gerada, pode comprometer a legibilidade do seu código, além de te dar uma estrutura vazia.
É tentador pensar que essa criação de lista também vai ter um impacto na performance. Isso eu deixo como Tarefa aos Leitores (TM). Dica: Profiling and Timing. Fiquem à vontade para discutir isso comigo no twitter também
Outro ponto de cuidado é o aninhamento extremo de coisas em uma comprehension. É possível fazer muita coisa com "one-liner", os código de uma linha só, e tem gente que adora coisas do tipo. Eu particularmente acho legal o poder que Python e outras linguagens tem de fazer isso, mas para códigos em produção que serão mantidos futuramente, eu evito. A foto de capa desse artigo é um péssimo exemplo de comprehension.
A legibilidade do código é uma propriedade extremamente importante em equipes de software. Tomem cuidado com isso e não tenha medo de usar de code reviews para discutir com seus colegas e validar que sua solução está legível! Legibilidade é um conceito subjetivo e divergências podem acontecer.
Criando Dicionários com Comprehensions
Assim como listas, dicionários também podem ser criados com comprehensions. Suponha que eu queira construir um dicionário onde a chave é um número e o valor é esse número ao quadrado. Podemos fazer com um loop for normal:
quadrados = {}
for i in range(100):
quadrados[i] = i ** 2
Esse código irá nos dar o dicionário:
{0: 0,
1: 1,
2: 4,
3: 9,
4: 16,
5: 25,
6: 36,
7: 49,
8: 64,
9: 81,
10: 100,
(...)
Observe que dicionários, diferente de listas, são inicializados com { }
ao invés de [ ]
. Isso será refletido na dict comprehension também:
quadrados = {i: i**2 for i in range(100)}
Note como essa estrutura é muito similar à de uma list comprehension, com a diferença que agora temos um par chave: valor
. Podemos ler esse código como
"Adicionei
como chave,i ao quadrado
como valor, para cadai
de 0 até 99"
Filtrando Dicionários
Temos nosso dicionário de números e seus valores ao quadrado! Vamos fazer duas separações ― separar todas as chaves pares e separar todos os valores que são maiores que 100. Como dicionários, ao contrário de listas, possuem duas "entidades" para cada entrada, podemos filtrar por cada uma delas.
Para filtrar as chaves pares, podemos fazer algo do tipo:
chaves_pares = {}
for chave in quadrados:
if chave % 2 == 0:
chaves_pares[chave] = quadrados[chave]
Esse código vai nos dar:
{0: 0,
2: 4,
4: 16,
6: 36,
8: 64,
10: 100,
12: 144,
14: 196,
16: 256,
(...)
"Bidu, mas e o método items
em dicionários?" Vamo um passo de cada vez :D
Esse loop for pode ser reescrito de maneira similar com a filtragem de listas:
chaves_pares = {chave: quadrados[chave] for chave in quadrados if chave % 2 == 0}
De novo, temos uma estrutura que evolui da criação normal usando comprehensions, acrescentando uma condicional ao final. Podemos ler como
"Adicionechave
com o mesmo valor que ela possui emquadrados
, para cadachave
emquadrados
, se o resto da divisão da chave por 2 for 0"
Filtrando Valores
O segundo problema que propus foi filtrar todos os valores maiores do que 100. Uma iteração comum em dicionários, for x in dicionário
, faz um loop através apenas das chaves. Porém, podemos utilizar da seguinte sintaxe: for chave, valor in dicionário.items()
. Ela nos permite iterar pelas chaves e valores de uma só vez:
data = {"nome": "Bidu", "país": "Brasil"}
for chave, valor in data.items():
print(f"O valor de {chave} é {valor}")
Esse código imprime
O valor de nome é Bidu
O valor de país é Brasil
Nosso filtro pode ser implementado assim:
valores_grandes = {}
for chave, valor in quadrados.items():
if valor > 100:
valores_grandes[chave] = valor
Esse código irá nos retornar:
{11: 121,
12: 144,
13: 169,
14: 196,
15: 225,
16: 256,
17: 289,
18: 324,
(...)
E ele também pode virar uma comprehension!
valores_grandes = {c: v for c, v in quadrados.items() if v > 100}
Essa comprehension pode ser lida como
"Adicionechave
com o mesmovalor
que ela possui emquadrados
, para cadachave
evalor
emquadrados
, sevalor
for maior do que 100
Resumo
Nesse post eu discuti sobre como comprehensions são uma sintaxe útil do Python para quando queremos criar estruturas com base em outras ― seja filtrando ou transformando os valores originais ― ou com base em repetições de uma certa computação.
Fica também o alerta quanto ao abuso dessas estruturas, comprehensions não são um "loop for chique", elas servem apenas para substituir um tipo específico de loop ― quando queremos criar uma estrutura nova.
Não são apenas listas e dicionários que suportam comprehensions, elas também são compatíveis com outras estruturas como sets e tuplas!