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.

autores = ["Ramalho", "Bostrom", "Larson", "Pola", "Banks"]

dados = {
	"nome": "Bidu",
    "país": "Brasil"
}
Exemplo de uma lista e um dicionário

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:

  1. Criar uma lista com base na repetição de uma chamada de função
  2. 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

"Adicione i em selecionados onde i são os elementos de n 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

"Adicione i como chave, i ao quadrado como valor, para cada i 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

"Adicione chave com o mesmo valor que ela possui em quadrados, para cada chave em quadrados, 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

"Adicione chave com o mesmo valor que ela possui em quadrados, para cada chave e valor em quadrados, se valor 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!