danielsarmiento.dev

Las maravillas de las comprehensions en Python

February 19, 2020

Según la documentación oficial de Python, las comprensiones de listas nos permiten “crear listas de una manera concisa”. Sin embargo ya veremos ahora que no sólo vale para listas, sino también para otras estructuras iterables como tuplas o diccionarios. Sea cual sea el objeto de estas, las comprensiones son una herramienta muy útil para simplificar código en Python.

Comprensiones en listas

El primer objeto de las comprensiones son las listas. Muchas veces, a la hora de crear listas necesitamos realizar operaciones con valores. En estos casos suele hacerse uso de variables auxiliares que siguen existiendo despúes de ser usadas.

halves = []
for x in range(10):
    halves.append(x/2)

>>> halves
[0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5]

En el ejemplo anterior vemos que han sido necesarias tres líneas de código y una variable auxiliar x para simplemente rellenar una lista con las mitades de los valores del intervalo [0, 10). Con las comprensiones podemos resumir esto en una simple línea.

halves = [x/2 for x in range(10)]

>>> halves
[0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5]

A pesar de la sencillez del ejemplo, ya se puede observar que esta porción de código es mucho más pythónica que el anterior. Sin embargo, para poder apreciar la utilidad de esta característica será mejor ilustrarlo con ejemplos más complejos.

Comprensiones en listas y strings

Otro ejemplo bastante útil es el de generar tuplas de valores provenientes de dos listas. Por ejemplo para generar las posiciones de coordenadas (x, y) de un tablero de ajedrez.

board = []

for x in 'abcdefgh':
    for y in range(1, 9):
        board.append((x, y))

>>> board
[('a', 1), ('a', 2), ('a', 3), ('a', 4), ('a', 5), ('a', 6), ('a', 7), ('a', 8),
('b', 1), ('b', 2), ('b', 3), ('b', 4), ('b', 5), ('b', 6), ('b', 7), ('b', 8),
('c', 1), ('c', 2), ('c', 3), ('c', 4), ('c', 5), ('c', 6), ('c', 7), ('c', 8),
('d', 1), ('d', 2), ('d', 3), ('d', 4), ('d', 5), ('d', 6), ('d', 7), ('d', 8),
('e', 1), ('e', 2), ('e', 3), ('e', 4), ('e', 5), ('e', 6), ('e', 7), ('e', 8),
('f', 1), ('f', 2), ('f', 3), ('f', 4), ('f', 5), ('f', 6), ('f', 7), ('f', 8),
('g', 1), ('g', 2), ('g', 3), ('g', 4), ('g', 5), ('g', 6), ('g', 7), ('g', 8),
('h', 1), ('h', 2), ('h', 3), ('h', 4), ('h', 5), ('h', 6), ('h', 7), ('h', 8)]

De nuevo, con una comprensión de listas y strings podemos generar la lista de tuplas deseada en una sola línea de código:

board = []

[(x, y) for x in 'abcdefgh' for y in range(1,9)]

>>> board
[('a', 1), ('a', 2), ('a', 3), ('a', 4), ('a', 5), ('a', 6), ('a', 7), ('a', 8),
('b', 1), ('b', 2), ('b', 3), ('b', 4), ('b', 5), ('b', 6), ('b', 7), ('b', 8),
('c', 1), ('c', 2), ('c', 3), ('c', 4), ('c', 5), ('c', 6), ('c', 7), ('c', 8),
('d', 1), ('d', 2), ('d', 3), ('d', 4), ('d', 5), ('d', 6), ('d', 7), ('d', 8),
('e', 1), ('e', 2), ('e', 3), ('e', 4), ('e', 5), ('e', 6), ('e', 7), ('e', 8),
('f', 1), ('f', 2), ('f', 3), ('f', 4), ('f', 5), ('f', 6), ('f', 7), ('f', 8),
('g', 1), ('g', 2), ('g', 3), ('g', 4), ('g', 5), ('g', 6), ('g', 7), ('g', 8),
('h', 1), ('h', 2), ('h', 3), ('h', 4), ('h', 5), ('h', 6), ('h', 7), ('h', 8)]

Condiciones en comprensiones

Hasta ahora hemos seguido la siguiente estructura para las comprensiones de listas:

new_list = [expression for member in iterable]

Sin embargo esto es tan solo la parte obligatoria y permite hacer muchas más cosas con la estructura completa cuya parte opcional permite añadir una estructura condicional tipo if-elif-else:

new_list = [expression for member in iterable (if conditional)]

Para asignar a una lista los divisores de 3 en una rango, por ejemplo, podríamos hacerlo mediante un bucle for y una estructura de control de flujo tipo if-else:

div_by_3 = []
for num in range(30):
    if num % 3 == 0:
        div_by_3.append(num)

>>> div_by_3
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

En cambio, con una comprensión de listas podríamos tener:

div_by_3 = [num for num in range(30) if num % 3 == 0]

>>> div_by_3
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

Para estructuras condicionales más complejas del tipo if-elif-else podemos reutilizar el ejemplo anterior y añadir condiciones. En este caso, la estructura cambia un poco quedando así:

[expression if condition else expression for x in iterable]

Podríamos ampliar el ejemplo anterior ahora para que los números no divisibles por 3 los sustituyera por un -1:

div_by_3 = []
for num in range(30):
    if num % 3 == 0:
        div_by_3.append(num)
    else:
        div_by_3.append(-1)

>>> div_by_3
[0, -1, -1, 3, -1, -1, 6, -1, -1, 9, -1, -1, 12, -1, -1, 15, -1, -1, 18, -1, -1, 21, -1, -1, 24, -1, -1, 27, -1, -1]

Y con comprensión de listas:

div_by_3 = [num if num % 3 == 0 else -1 for num in range(30)]

>>> div_by_3
[0, -1, -1, 3, -1, -1, 6, -1, -1, 9, -1, -1, 12, -1, -1, 15, -1, -1, 18, -1, -1, 21, -1, -1, 24, -1, -1, 27, -1, -1]

Listas y diccionarios

Las comprensiones de listas y diccionarios me parecen de las mejores maneras de aprovechar esta funcionalidad de Python. Si quisiéramos construir un diccionario con la estructura {key: len(key)} prescindiendo de comprensiones tendríamos que hacer lo siguiente:

names = ["Daniel", "Sergio", "Alberto", "Francisco"]
name_length = {}
for name in names:
    name_length[name] = len(name)

>>> names_length
{'Daniel': 6, 'Sergio': 6, 'Alberto': 7, 'Francisco': 9}

De manera similar a los ejemplos anteriores, con las comprensiones podemos resumir la lógica del código anterior a una sola línea de código concisa.

name_length = {name: len(name) for name in ["Daniel", "Sergio", "Alberto", "Francisco"]}

>>> name_length
{'Daniel': 6, 'Sergio': 6, 'Alberto': 7, 'Francisco': 9}

En el caso de las comprensiones de diccionarios, suelo encontrarlas muy útiles para inicializar diccionarios con valore por defecto. Por ejemplo, en el caso de un diccionario que representara los valores de intensidad de los canales rojo, verde y azul (RGB) se podría inicializar el color negro (todos los valores a cero) de la siguiente manera:

rgb = {channel: 0 for channel in ["red", "green", "blue"]}
>>> rgb
{'red': 0, 'green': 0, 'blue': 0}

En resumen: las comprensiones de Python son una característica elegante que permite mejorar la legibilidad del código y reducir el tamaño de muchas funciones que tratan la creación de listas y diccionarios.


Software Developer based in Madrid. This is my personal blog. I am also on Twitter and LinkedIn.