Python Language
Listar comprensiones
Buscar..
Introducción
Las comprensiones de listas en Python son construcciones sintácticas concisas. Se pueden utilizar para generar listas de otras listas aplicando funciones a cada elemento de la lista. La siguiente sección explica y demuestra el uso de estas expresiones.
Sintaxis
- [x + 1 para x en (1, 2, 3)] # comprensión de lista, da [2, 3, 4]
- (x + 1 para x en (1, 2, 3)) # expresión del generador, producirá 2, luego 3, luego 4
- [x para x en (1, 2, 3) si x% 2 == 0] # enumerar comprensión con filtro, da [2]
- [x + 1 si x% 2 == 0 sino x para x en (1, 2, 3)] # lista comprensión con ternario
- [x + 1 si x% 2 == 0 sino x para x en el rango (-3,4) si x> 0] # lista comprensión con ternario y filtrado
- {x para x en (1, 2, 2, 3)} # establecer comprensión, da {1, 2, 3}
- {k: v para k, v en [('a', 1), ('b', 2)]} # dict comprensión, da {'a': 1, 'b': 2} (python 2.7+ y 3.0+ solamente)
- [x + y para x en [1, 2] para y en [10, 20]] # Bucles anidados, da [11, 21, 12, 22]
- [x + y para x en [1, 2, 3] si x> 2 para y en [3, 4, 5]] # Condición verificada al principio para bucle
- [x + y para x en [1, 2, 3] para y en [3, 4, 5] si x> 2] # Condición verificada en 2da para bucle
- [x para x en xrange (10) si x% 2 == 0] # Condición verificada si los números en bucle son números impares
Observaciones
Las comprensiones son construcciones sintácticas que definen estructuras de datos o expresiones exclusivas de un idioma en particular. El uso adecuado de las comprensiones las reinterpreta en expresiones fáciles de entender. Como expresiones, se pueden utilizar:
- en el lado derecho de las tareas
- como argumentos para llamadas de funcion
- en el cuerpo de una función lambda
- como declaraciones independientes. (Por ejemplo:
[print(x) for x in range(10)]
)
Lista de Comprensiones
Una lista de comprensión crea una nueva list
al aplicar una expresión a cada elemento de un iterable . La forma más básica es:
[ <expression> for <element> in <iterable> ]
También hay una condición opcional "si":
[ <expression> for <element> in <iterable> if <condition> ]
Cada <element>
en el <iterable>
se conecta a la <expression>
si el <condition>
(opcional) se evalúa como verdadero . Todos los resultados se devuelven a la vez en la nueva lista. Las expresiones de los generadores se evalúan perezosamente, pero las comprensiones de la lista evalúan todo el iterador inmediatamente, consumiendo memoria proporcional a la longitud del iterador.
Para crear una list
de enteros cuadrados:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
La expresión for
establece x
a cada valor por turno de (1, 2, 3, 4)
. El resultado de la expresión x * x
se anexa a una list
interna. La list
interna se asigna a los squares
variables cuando se completa.
Además de un aumento de velocidad (como se explica aquí ), una comprensión de la lista es aproximadamente equivalente al siguiente bucle for:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
La expresión aplicada a cada elemento puede ser tan compleja como sea necesario:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
más
else
se puede utilizar en las construcciones de comprensión de listas, pero tenga cuidado con la sintaxis. Las cláusulas if / else deben usarse antes for
bucle, no después:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Tenga en cuenta que esto utiliza una construcción de lenguaje diferente, una expresión condicional , que en sí misma no es parte de la sintaxis de comprensión . Considerando que el if
after the for…in
es parte de la lista de comprensión y se utiliza para filtrar elementos de la fuente iterable.
Doble iteración
El orden de doble iteración [... for x in ... for y in ...]
es natural o contraintuitivo. La regla de oro es seguir un equivalente for
bucle:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Esto se convierte en:
[str(x)
for i in range(3)
for x in foo(i)
]
Esto se puede comprimir en una línea como [str(x) for i in range(3) for x in foo(i)]
Mutación in situ y otros efectos secundarios
Antes de usar la comprensión de lista, comprenda la diferencia entre las funciones solicitadas por sus efectos secundarios (funciones mutantes o en el lugar ) que generalmente devuelven None
y las funciones que devuelven un valor interesante.
Muchas funciones (especialmente funciones puras ) simplemente toman un objeto y devuelven algún objeto. Una función en el lugar modifica el objeto existente, que se denomina efecto secundario . Otros ejemplos incluyen operaciones de entrada y salida, como la impresión.
list.sort()
ordena una lista en el lugar (lo que significa que modifica la lista original) y devuelve el valor None
. Por lo tanto, no funcionará como se espera en una lista de comprensión:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
En su lugar, sorted()
devuelve una list
ordenada en lugar de ordenar in situ:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
Es posible el uso de comprensiones para efectos secundarios, como I / O o funciones in situ. Sin embargo, un bucle for suele ser más legible. Mientras esto funciona en Python 3:
[print(x) for x in (1, 2, 3)]
En su lugar, utilice:
for x in (1, 2, 3):
print(x)
En algunas situaciones, las funciones de efectos secundarios son adecuadas para la comprensión de listas. random.randrange()
tiene el efecto secundario de cambiar el estado del generador de números aleatorios, pero también devuelve un valor interesante. Además, se puede llamar a next()
en un iterador.
El siguiente generador de valores aleatorios no es puro, pero tiene sentido ya que el generador aleatorio se restablece cada vez que se evalúa la expresión:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Los espacios en blanco en la lista de comprensión
Las comprensiones de listas más complicadas pueden alcanzar una longitud no deseada o volverse menos legibles. Aunque es menos común en los ejemplos, es posible dividir una lista de comprensión en varias líneas, como por ejemplo:
[
x for x
in 'foo'
if x not in 'bar'
]
Diccionario de Comprensiones
Una comprensión de diccionario es similar a una comprensión de lista, excepto que produce un objeto de diccionario en lugar de una lista.
Un ejemplo básico:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
que es solo otra forma de escribir:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Al igual que con una lista de comprensión, podemos usar una declaración condicional dentro de la comprensión de dict para producir solo los elementos de dict que cumplan con algún criterio.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
O, reescrito usando una expresión generadora.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
Comenzando con un diccionario y utilizando la comprensión del diccionario como un filtro de par clave-valor
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Tecla de conmutación y valor del diccionario (diccionario invertido)
Si tiene un dict que contiene valores hashables simples (los valores duplicados pueden tener resultados inesperados):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
y quería intercambiar las claves y los valores, puede adoptar varios enfoques dependiendo de su estilo de codificación:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Si su diccionario es grande, considere la importación itertools y utilizar izip
o imap
.
Fusionando diccionarios
Combine diccionarios y, opcionalmente, anule valores antiguos con una comprensión de diccionario anidado.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Sin embargo, el desempaque del diccionario ( PEP 448 ) puede ser el preferido.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Nota : las comprensiones de diccionarios se agregaron en Python 3.0 y se respaldaron a 2.7+, a diferencia de las comprensiones de listas, que se agregaron en 2.0. Las versiones <2.7 pueden usar expresiones generadoras y el dict()
incorporado para simular el comportamiento de las comprensiones de diccionario.
Expresiones del generador
Las expresiones generadoras son muy similares a las listas de comprensión. La principal diferencia es que no crea un conjunto completo de resultados a la vez; crea un objeto generador que luego puede ser iterado.
Por ejemplo, vea la diferencia en el siguiente código:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Estos son dos objetos muy diferentes:
la lista de comprensión devuelve un objeto de
list
mientras que la comprensión del generador devuelve ungenerator
.generator
objetosgenerator
no se pueden indexar y hace uso de lanext
función para ordenar los artículos.
Nota : Utilizamos xrange
ya que también crea un objeto generador. Si usaríamos el rango, se crearía una lista. Además, xrange
solo existe en la versión posterior de python 2. En python 3, range
solo devuelve un generador. Para obtener más información, consulte el ejemplo de Diferencias entre las funciones de rango y rango .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
NOTA: La función
g.next()
debe sustituirse pornext(g)
yxrange
conrange
ya queIterator.next()
yxrange()
no existen en Python 3.
Aunque ambos pueden ser iterados de manera similar:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Casos de uso
Las expresiones del generador se evalúan perezosamente, lo que significa que generan y devuelven cada valor solo cuando el generador está iterado. Esto suele ser útil cuando se itera a través de grandes conjuntos de datos, evitando la necesidad de crear un duplicado del conjunto de datos en la memoria:
for square in (x**2 for x in range(1000000)):
#do something
Otro caso de uso común es evitar la iteración en todo un iterable si no es necesario hacerlo. En este ejemplo, un elemento se recupera de una API remota con cada iteración de get_objects()
. Pueden existir miles de objetos, deben recuperarse uno por uno, y solo necesitamos saber si existe un objeto que coincida con un patrón. Al usar una expresión generadora, cuando encontramos un objeto que coincide con el patrón.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Establecer Comprensiones
La comprensión del conjunto es similar a la lista y la comprensión del diccionario , pero produce un conjunto , que es una colección desordenada de elementos únicos.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Tenga en cuenta que los conjuntos están desordenados. Esto significa que el orden de los resultados en el conjunto puede diferir del presentado en los ejemplos anteriores.
Nota : la comprensión de configuración está disponible desde python 2.7+, a diferencia de las comprensiones de lista, que se agregaron en 2.0. En Python 2.2 a Python 2.6, la función set()
se puede usar con una expresión generadora para producir el mismo resultado:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Evite operaciones repetitivas y costosas usando cláusula condicional
Considere la siguiente lista de comprensión:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Esto da como resultado dos llamadas a f(x)
para 1,000 valores de x
: una llamada para generar el valor y la otra para verificar la condición if
. Si f(x)
es una operación particularmente costosa, esto puede tener implicaciones significativas en el rendimiento. Peor aún, si llamar a f()
tiene efectos secundarios, puede tener resultados sorprendentes.
En su lugar, debe evaluar la operación costosa solo una vez para cada valor de x
generando un iterable intermedio ( expresión del generador ) de la siguiente manera:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
O, usando el mapa incorporado equivalente:
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Otra forma que podría resultar en un código más legible es colocar el resultado parcial ( v
en el ejemplo anterior) en un iterable (como una lista o una tupla) y luego iterar sobre él. Como v
será el único elemento en el iterable, el resultado es que ahora tenemos una referencia a la salida de nuestra función lenta calculada solo una vez:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
Sin embargo, en la práctica, la lógica del código puede ser más complicada y es importante mantenerlo legible. En general, se recomienda una función de generador por separado en un complejo de una sola línea:
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Otra manera de evitar el cálculo de f(x)
varias veces es utilizar el @functools.lru_cache()
(Python 3.2+) decorador en f(x)
. De esta manera, dado que la salida de f
para la entrada x
ya se ha calculado una vez, la invocación de la segunda función de la comprensión de la lista original será tan rápida como la búsqueda de un diccionario. Este enfoque utiliza la memoria para mejorar la eficiencia, que es comparable al uso de expresiones generadoras.
Di que tienes que aplanar una lista
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Algunos de los métodos podrían ser:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Sin embargo, la comprensión de la lista proporcionaría la mejor complejidad de tiempo.
[item for sublist in l for item in sublist]
Los accesos directos basados en + (incluido el uso implícito en la suma) son, por necesidad, O (L ^ 2) cuando hay L sublistas: a medida que la lista de resultados intermedios se hace más larga, en cada paso se obtiene un nuevo objeto de lista de resultados intermedios. asignados, y todos los elementos en el resultado intermedio anterior deben copiarse (así como algunos nuevos agregados al final). Entonces (por simplicidad y sin pérdida de generalidad real) digamos que tiene L sublistas de I elementos cada uno: los primeros elementos I se copian una y otra vez L-1 veces, el segundo I elementos L-2 veces, y así sucesivamente; el número total de copias es I veces la suma de x para x de 1 a L excluidas, es decir, I * (L ** 2) / 2.
La lista de comprensión solo genera una lista, una vez, y copia cada elemento (de su lugar de residencia original a la lista de resultados) también una sola vez.
Comprensiones que involucran tuplas
La cláusula for
de una lista de comprensión puede especificar más de una variable:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
Esto es como regular for
bucles:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Sin embargo, tenga en cuenta que si la expresión que comienza la comprensión es una tupla, debe estar entre paréntesis:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Contando Ocurrencias Usando Comprensión
Cuando queremos contar el número de elementos en un iterable, que cumplan con alguna condición, podemos usar la comprensión para producir una sintaxis idiomática:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
El concepto básico se puede resumir como:
- Iterar sobre los elementos en
range(1000)
. - Concatenar todo lo necesario
if
condiciones. - Utilice 1 como expresión para devolver un 1 por cada elemento que cumpla con las condiciones.
- Resuma todos los
1
s para determinar la cantidad de elementos que cumplen con las condiciones.
Nota : Aquí no estamos recolectando los 1
s en una lista (note la ausencia de corchetes), pero pasamos los directamente a la función de sum
que los está sumando. Esto se denomina expresión generadora , que es similar a una comprensión.
Cambio de tipos en una lista
Los datos cuantitativos a menudo se leen como cadenas que deben convertirse en tipos numéricos antes de procesarlos. Los tipos de todos los elementos de la lista se pueden convertir con una Comprensión de lista o la función map()
.
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]