Python Language
Lista de Comprensiones
Buscar..
Introducción
[i ** 2 for i in range(1,11)]
El dummy i
de un range
lista existente se usa para hacer un nuevo patrón de elemento. Se usa donde un bucle for sería necesario en lenguajes menos expresivos.
Sintaxis
- [i for i in range (10)] # lista de comprensión básica
- [i for i in xrange (10)] # lista de comprensión básica con objeto generador en Python 2.x
- [i for i in range (20) if i% 2 == 0] # con filtro
- [x + y para x en [1, 2, 3] para y en [3, 4, 5]] # bucles anidados
- [i if i> 6 else 0 for i in range (10)] # expresión ternaria
- [i if i> 4 else 0 para i en el rango (20) if i% 2 == 0] # con filtro y expresión ternaria
- [[x + y para x en [1, 2, 3]] para y en [3, 4, 5]] # comprensión de lista anidada
Observaciones
La lista de comprensión se describió en PEP 202 y se introdujo en Python 2.0.
Lista de comprensiones condicionales
Dada una lista de comprensión , puede agregar uno o más if
condiciones para filtrar los valores.
[<expression> for <element> in <iterable> if <condition>]
Para cada <element>
en <iterable>
; Si <condition>
evalúa como True
, agregue <expression>
(generalmente una función de <element>
) a la lista devuelta.
Por ejemplo, esto se puede usar para extraer solo números pares de una secuencia de enteros:
[x for x in range(10) if x % 2 == 0]
# Out: [0, 2, 4, 6, 8]
El código anterior es equivalente a:
even_numbers = []
for x in range(10):
if x % 2 == 0:
even_numbers.append(x)
print(even_numbers)
# Out: [0, 2, 4, 6, 8]
Además, una comprensión condicional de la lista de la forma [e for x in y if c]
(donde e
y c
son expresiones en términos de x
) es equivalente a list(filter(lambda x: c, map(lambda x: e, y)))
.
A pesar de proporcionar el mismo resultado, preste atención al hecho de que el ejemplo anterior es casi 2 veces más rápido que el segundo. Para aquellos que tienen curiosidad, esta es una buena explicación de la razón.
Tenga en cuenta que esto es bastante diferente de la expresión condicional ... if ... else ...
(a veces conocida como expresión ternaria ) que puede usar para la parte <expression>
de la lista de comprensión. Considere el siguiente ejemplo:
[x if x % 2 == 0 else None for x in range(10)]
# Out: [0, None, 2, None, 4, None, 6, None, 8, None]
Aquí, la expresión condicional no es un filtro, sino un operador que determina el valor que se utilizará para los elementos de la lista:
<value-if-condition-is-true> if <condition> else <value-if-condition-is-false>
Esto se vuelve más obvio si lo combinas con otros operadores:
[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Si está utilizando Python 2.7, xrange
puede ser mejor que el range
por varios motivos, como se describe en la documentación de xrange
.
[2 * (x if x % 2 == 0 else -1) + 1 for x in xrange(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
El código anterior es equivalente a:
numbers = []
for x in range(10):
if x % 2 == 0:
temp = x
else:
temp = -1
numbers.append(2 * temp + 1)
print(numbers)
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Se puede combinar expresiones ternarios y if
las condiciones. El operador ternario trabaja en el resultado filtrado:
[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
# Out: ['*', '*', 4, 6, 8]
Lo mismo no podría haberse logrado solo por el operador ternario:
[x if (x > 2 and x % 2 == 0) else '*' for x in range(10)]
# Out:['*', '*', '*', '*', 4, '*', 6, '*', 8, '*']
Vea también: Filtros , que a menudo proporcionan una alternativa suficiente a las comprensiones de listas condicionales.
Lista de Comprensiones con Bucles Anidados
Las Comprensiones de lista pueden usar anidados for
bucles. Puede codificar cualquier número de bucles for anidados dentro de una lista por comprensión, y cada uno for
bucle puede tener una opción asociada if
la prueba. Al hacerlo, el orden de la for
las construcciones es del mismo orden que la hora de escribir una serie de anidado for
declaraciones. La estructura general de las listas de comprensión se ve así:
[ expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]...
for targetN in iterableN [if conditionN] ]
Por ejemplo, el siguiente código aplanamiento una lista de listas utilizando múltiples for
declaraciones:
data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
for element in each_list:
output.append(element)
print(output)
# Out: [1, 2, 3, 4, 5, 6]
se puede escribir de forma equivalente como una lista de comprensión con múltiples for
construcciones:
data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
# Out: [1, 2, 3, 4, 5, 6]
Tanto en la forma expandida como en la lista de comprensión, el bucle externo (primero para la declaración) aparece primero.
Además de ser más compacto, la comprensión anidada también es significativamente más rápida.
In [1]: data = [[1,2],[3,4],[5,6]]
In [2]: def f():
...: output=[]
...: for each_list in data:
...: for element in each_list:
...: output.append(element)
...: return output
In [3]: timeit f()
1000000 loops, best of 3: 1.37 µs per loop
In [4]: timeit [inner for outer in data for inner in outer]
1000000 loops, best of 3: 632 ns per loop
La sobrecarga para la llamada de función anterior es de aproximadamente 140 ns .
En línea if
s están anidados de manera similar, y puede ocurrir en cualquier posición después de la primera for
:
data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
if len(each_list) == 2
for element in each_list
if element != 5]
print(output)
# Out: [2, 3, 4]
Sin embargo, por razones de legibilidad, debe considerar el uso de bucles for tradicionales. Esto es especialmente cierto cuando el anidamiento tiene más de 2 niveles de profundidad y / o la lógica de la comprensión es demasiado compleja. la comprensión de múltiples listas de bucles anidadas podría ser propensa a errores o dar un resultado inesperado.
Refactorización de filtro y mapa para enumerar las comprensiones.
Las funciones de filter
o map
menudo deben ser reemplazadas por listas de comprensión . Guido Van Rossum describe esto bien en una carta abierta en 2005 :
filter(P, S)
casi siempre se escribe más claro como[x for x in S if P(x)]
, y esto tiene la gran ventaja de que los usos más comunes incluyen predicados que son comparaciones, por ejemplo,x==42
, y la definición de un lambda para eso solo requiere mucho más esfuerzo para el lector (más el lambda es más lento que la lista de comprensión). Más aún para elmap(F, S)
que se convierte en[F(x) for x in S]
. Por supuesto, en muchos casos podrías usar expresiones generadoras.
Las siguientes líneas de código se consideran " no pythonic " y generarán errores en muchos linters de python.
filter(lambda x: x % 2 == 0, range(10)) # even numbers < 10
map(lambda x: 2*x, range(10)) # multiply each number by two
reduce(lambda x,y: x+y, range(10)) # sum of all elements in list
Tomando lo que hemos aprendido de la cita anterior, podemos desglosar estas expresiones de filter
y map
en sus listas de comprensión equivalentes; También elimina las funciones lambda de cada una, lo que hace que el código sea más legible en el proceso.
# Filter:
# P(x) = x % 2 == 0
# S = range(10)
[x for x in range(10) if x % 2 == 0]
# Map
# F(x) = 2*x
# S = range(10)
[2*x for x in range(10)]
La legibilidad se vuelve aún más evidente cuando se trata de funciones de encadenamiento. Donde debido a la legibilidad, los resultados de un mapa o función de filtro deben pasarse como resultado al siguiente; con casos simples, estos pueden ser reemplazados por una sola lista de comprensión. Además, podemos decir fácilmente de la comprensión de la lista cuál es el resultado de nuestro proceso, dónde hay más carga cognitiva al razonar sobre el proceso de Mapa y Filtro encadenado.
# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2*x, filtered)
# List comprehension
results = [2*x for x in range(10) if x % 2 == 0]
Refactorización - Referencia rápida
Mapa
map(F, S) == [F(x) for x in S]
Filtrar
filter(P, S) == [x for x in S if P(x)]
donde F
y P
son funciones que transforman respectivamente los valores de entrada y devuelven un bool
Comprensiones de lista anidadas
Las comprensiones de listas anidadas, a diferencia de las comprensiones de listas con bucles anidados, son comprensiones de listas dentro de una comprensión de listas. La expresión inicial puede ser cualquier expresión arbitraria, incluida otra lista de comprensión.
#List Comprehension with nested loop
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
#Out: [4, 5, 6, 5, 6, 7, 6, 7, 8]
#Nested List Comprehension
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
#Out: [[4, 5, 6], [5, 6, 7], [6, 7, 8]]
El ejemplo anidado es equivalente a
l = []
for y in [3, 4, 5]:
temp = []
for x in [1, 2, 3]:
temp.append(x + y)
l.append(temp)
Un ejemplo donde se puede usar una comprensión anidada para transponer una matriz.
matrix = [[1,2,3],
[4,5,6],
[7,8,9]]
[[row[i] for row in matrix] for i in range(len(matrix))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Al igual que anidado for
bucles, no hay límite a cómo se pueden anidar las comprensiones profundas.
[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
# Out: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]
Iterar dos o más listas simultáneamente dentro de la comprensión de la lista
Para iterar más de dos listas simultáneamente dentro de la comprensión de la lista , se puede usar zip()
como:
>>> list_1 = [1, 2, 3 , 4]
>>> list_2 = ['a', 'b', 'c', 'd']
>>> list_3 = ['6', '7', '8', '9']
# Two lists
>>> [(i, j) for i, j in zip(list_1, list_2)]
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
# Three lists
>>> [(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]
[(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]
# so on ...