List comprehensions, em ciência da computação, é um construtor de listas que se baseia em outras listas pré-existentes.
Esse conceito não é novo e data dos anos 60's. Mas o termo
comprehension foi usado pela primeira vez na linguagem
NPL.
E as List Comprehension tem como origem as representação do
conjuntos da matemática:
{x ∈ N | 0 < x < 7}
Lê-se: x pertence ao conjunto dos Naturais, tal que 0 é menor que x e x menor que 7. O que restringiria esse conjunto a {1, 2, 3, 4, 5, 6}.
Em
Python, uma List comprehension tem a seguinte sintaxe:
[x * 2 for x in lista]
Nesse exemplo abaixo, multiplicamos os itens da
lista por 2, atribuimos à variável
nova_lista:
>>> lista = [1, 2, 3, 4, 5]
>>> nova_lista = [x * 2 for x in lista]
>>> nova_lista
[2, 4, 6, 8, 10]
Sendo que o '
x * 2' é executado em cada item da lista. Um código mais limpo e intuitivo do que uma versão usando a builtin function
map.
>>> def vezes_2(n):
... return n * 2
...
...
>>> map(vezes_2, range(6))
[0, 2, 4, 6, 8, 10]
Poderiamos fazer essa operação com uma
lambda function.
>>> map(lambda x: x * 2, range(6))
[0, 2, 4, 6, 8, 10]
Um código análogo sem List comprehension:
>>> lista = [1, 2, 3, 4, 5]
>>> nova_lista = []
>>> for x in lista:
... nova_lista.append(x * 2)
...
...
>>>
>>> nova_lista
[2, 4, 6, 8, 10]
Dentro das List comprehensions ainda se pode usar condicionais, para filtrar elementos na criação das novas listas.
>>> frase = ['o', 'rato', 'roeu', 'a', 'roupa', 'do', 'rei', 'de', 'roma']
>>> [p for p in frase if p[0] == 'r']
['rato', 'roeu', 'roupa', 'rei', 'roma']
Aqui eu verifico se a primeira letra da palavra é 'r'.
Criei um pequeno script para comparar a performance das List comprehensions. Escrevi duas funções, uma usando List comprehensions e outra não, que criam uma lista com os números pares entre 0 e 1000000. Para fazer a comparação usei a biblioteca
cProfile.
def foo():
l = []
for i in range(1000000):
if i % 2 == 0:
l.append(i)
def foo_listcomp():
l = [i for i in range(1000000) if i % 2 == 0]
cProfile.run('foo()')
cProfile.run('foo_listcomp()')
Executando o código.
$ python bench.py
500004 function calls in 1.739 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.009 0.009 1.739 1.739 <string>:1(<module>)
1 0.936 0.936 1.730 1.730 bench.py:61(foo)
500000 0.769 0.000 0.769 0.000 {method 'append' of 'list'...
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof...
1 0.025 0.025 0.025 0.025 {range}
4 function calls in 0.165 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.015 0.015 0.165 0.165 :1()
1 0.125 0.125 0.150 0.150 bench.py:67(foo_listcomp)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof...
1 0.024 0.024 0.024 0.024 {range}
O código convencional chega a ser 10 vezes mais lento que o código que utiliza List comprehension. 1.739 segundos contra 0.165.
A performance e a simplicidade do código justifica o uso das List comprehensions quando se quer criar listas a partir de listas, sua finalidade semântica. Mas quando a lógica envolvida na geração da nova lista é muito complexa, o seu uso pode diminuir a legibilidade do código drasticamente.
Referências:
Google Style Guide
Documentação Python
The History of Python