No primeiro ou no segundo ano programando com Python, eu nunca usei list comprehension (na época, eram comprehensions apenas). Eu li sobre elas, meio que descobri como trabalhavam, e depois agarrei map() e filter(), os quais eu entendi. Olhando para trás, acho que isso tem muito a ver com o fato de que as explicações de comprehensions são feitas usando a sua origem – a matemática – em vez do domínio em que as usamos – programação.
Uma busca rápida no DuckDuckGo me diz que esse ainda é o caso – a Wikipedia nos pede para considerar algo como isto: S = {2 ? x | x?N | x> 3}, e outras fontes também parecem começar com ‘isso é como é feito em matemática, então … ‘ (uma exceção notável é o tutorial sobre python.org).
Ao falar com programadores, eu gostaria de explicar comprehensions de maneira diferente, porque nem todos eles têm um background em matemática. Para um programador, uma list comprehension é simplesmente um loop de for´s para constrir listas, usando uma notação mais declarativa do que um loop for usual. Para aqueles de nós que acostumados a map() e filter(), list comprehension são as duas coisas também.
Considere o seguinte:
def loop(my_list):
result = []
for x in my_list:
if x > 3:
result.append(x*2)
return result
Já escreveu alguma vez um código como esse? Esse é o código que afirma explicitamente que medidas devem ser tomadas para a construção de sua lista; mas você não precisa – em vez disso, você pode indicar o que você deseja:
def compr(my_list):
return [x*2 for x in my_list if x > 3]
Isso se traduz para me dar valor*2 para cada valor em my_list, mas apenas se esse valor for superior a três. Note também que essa expressão funciona com map (multiplipar por dois) e filter (usar apenas os valores que são menores de dois). O normal seria [acrescentar algo à lista for para cada valor em um iterável, opcionalmente apenas se uma condição if for True para aquele valor].
Comprehensions também funcionam aninhadas – considere este código simples, mas feio:
def create_matrix_loop(size, default):
new_matrix = []
for y in range(size):
row = []
for x in range(size):
row.append(default)
new_matrix.append(row)
return new_matrix
Exemplo de saída:
>create_matrix_loop(3, None)
[[None, None, None],
[None, None, None],
[None, None, None]]
Uma vez que comprehensions podem ser aninhadas, isso pode ser substituído por:
def create_matrix_compr(size, default):
return [[default for x in range(size)] for x in range(size)]
Como um bônus adicional, quando não dizemos ao compilador como queremos fazer algo, mas sim o que nós queremos que seja feito, pode gerar um melhor – mais rápido – bytecode para nós. A versão loop de create_matrix está traduzida em 35 instruções de bytecode, e a versão usando uma list comprehension é apenas 20 (tente import dis; dis.dis (func) para ver como func aparece em bytecode) e, na realidade, muitas vezes você vai evitar fazer uma função ao usar comprehensions, uma vez que elas são concisas o suficiente, fazendo essa diferença ainda maior. Ao cronometrar as implementações, a diferença é evidente:
>timeit -n100 create_matrix_loop(1000, None)
100 loops, best of 3: 113 ms per loop
>timeit -n100 create_matrix_compr(1000, None)
100 loops, best of 3: 49.1 ms per loop
É isso mesmo: menos código, sintaxe declarativa, e uma execução mais rápida! (Nota: Eu usei o iPython ao criar e cronometrar os exemplos – é impressionante e você deve experimentá-lo)
?
Texto original disponível em http://blaag.haard.se/Explaining-comprehensions-to-programmers/







