Python – Listas por comprensión – 1

Objetivo: introducir formalmente una técnica poderosa para la creación y modificación de listas: las listas por comprensión.

En un artículo reciente presentamos varios modos de abordar la necesidad de modificar una lista iterando sobre ella.

Échale un vistazo para situarte en contexto si no lo recuerdas; te llevará apenas un minuto.

Tras presentar las técnicas de modificación con la creación de una nueva lista temporal y mediante el acceso a través del índice, soltamos, a modo de aperitivo, un bombazo en perfecto estilo pythonesco:

>>> lista = [1, 2, 3, 4, 5]
>>> lista = [elemento * elemento for elemento in lista]
>>> lista
[1, 4, 9, 16, 25]

Nos ha bastado una única instrucción para transformar la lista por otra con sus elementos elevados al cuadrado.

Las listas por comprensión no sólo suponen un modo más elegante y rápido de escribir código, sino que además se ejecutan más rápidamente también, al estar implementadas directamente en lenguaje C.

Supongamos que tenemos una lista compuesta de cadenas de caracteres que queremos transformar en mayúsculas:

>>> alioli = ['ajo', 'aceite']

Para generar la lista por comprensión procedemos del siguiente modo:

1) Escribimos una iteración que recorra la lista:

for s in alioli

2) Agregamos a la izquierda lo que queremos que suceda con cada elemento:

s.upper() for s in alioli

3) Finalmente, rodeamos toda la expresión entre corchetes:

[s.upper() for s in alioli]

Observémoslo en acción:

>>> [s.upper() for s in alioli]
['AJO', 'ACEITE']

Es muy importante comprender que este procedimiento no modifica la lista original, que mantiene su valor.

>>> alioli
['ajo', 'aceite']

Se ha creado, por el contrario, un nuevo objeto lista que podría ser referenciado por cualquier otra variable.

Por supuesto, si lo que deseábamos era transformar la lista original podríamos haber hecho directamente la siguiente asignación:

>>> alioli = [s.upper() for s in alioli]

Ahora sí: alioli está referenciando la nueva lista por comprensión generada en lugar de la antigua:

>>> alioli
['AJO', 'ACEITE']

Una señora salsa en mayúsculas…

La iteración podemos realizarla sobre cualquier estructura susceptible de ser iterada, no necesariamente sobre una lista.

El siguiente ejemplo toma una cadena de caracteres y construye una lista con el código de cada carácter individual de la cadena:

>>> s = 'Me tomaría ahora una buena ración de papas al alioli'
>>> codigos = [ord(caracter) for caracter in s]
>>> codigos
[77, 101, 32, 116, 111, 109, 97, 114, 237, 97, 32, 97, 104, 111, 
114, 97, 32, 117, 110, 97, 32, 98, 117, 101, 110, 97, 32, 114, 
97, 99, 105, 243, 110, 32, 100, 101, 32, 112, 97, 112, 97, 115, 
32, 97, 108, 32, 97, 108, 105, 111, 108, 105]

Como ejercicio te propongo que realices la misma operación empleando la metodología tradicional.

Me ha entrado mucha hambre escribiendo esto, de modo que continuaremos hablando otro día de las listas por comprensión y presentaremos una nueva sintaxis ampliada que las hará más potentes aún.

Javier Montero Gabarró


Python – Listas por comprensión – 1


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python: Una función para iterar sobre listas anidadas

Objetivo: crear una función que ilustre cómo iterar sobre listas anidadas de cualquier profundidad.

Un lector del blog me planteaba ayer una interesante cuestión al hilo del artículo dedicado a la iteración sobre una lista con la sentencia for. Se preguntaba cómo proceder en el supuesto de que se tratara de listas anidadas en las que no conocemos a priori su profundidad de anidación.

Por ejemplo, supongamos que deseamos imprimir todos los enteros existentes en la siguiente lista:

lista_anidada = [1, [2, [3, 4, [5, 6], 7]], 8, [9, 10]]

Observa que el segundo elemento, que es el que presenta mayor nivel de anidación, es en realidad otra lista, cuyo segundo elemento es a su vez otra lista de la cual el tercer elemento es otra lista también. En este ejemplo en particular nos encontramos hasta cuatro niveles de profundidad. Nuestra función deberá ser genérica, en el sentido de que no conocerá de antemano la complejidad de la lista.

Una iteración ordinaria nos devolvería el siguiente resultado:

for elemento in lista_anidada:
    print(elemento)

1
[2, [3, 4, [5, 6], 7]]
8
[9, 10]

No nos sirve, pues pretendemos recuperar todos los elementos simples.

Procederemos de la siguiente forma:

Vamos a recorrer la lista de principio a fin. Si el elemento a tratar es una lista habrá que imprimir sus elementos individuales; si no lo es, imprimimos directamente su valor.

Observa con cuidado la frase “si el elemento a tratar es una lista habrá que imprimir sus elementos individuales”. Es exactamente el mismo problema que al inicio, salvo que está reducido a un subconjunto más reducido: la lista anidada dentro de la lista.

Al resolver esta segunda cuestión nos aparecería nuevamente una lista anidada, esta vez del tercer nivel; el problema seguiría siendo exactamente el mismo, pero cada vez más reducido.

Escenario perfecto para una función recursiva

Para resolver la parte que dice “si el elemento a tratar es una lista”, recurriremos a una función de Python que nos permite saber si un objeto es o no de un tipo determinado: isinstance().

Obsérvala en acción:

>>> a = 5
>>> b = 'casa'
>>> c = [1, 2]
>>> isinstance(a, list)
False
>>> isinstance(b, list)
False
>>> isinstance(c, list)
True

Implementar nuestra función resulta ya una tarea casi obvia:

def imprimir(lista):
    for elemento in lista:
        if isinstance(elemento, list):
            imprimir(elemento)
        else:
            print(elemento)

Fíjate en la técnica recursiva: si elemento es una lista, la función vuelve a llamarse a sí misma, pero esta vez sobre una lista anidada en el siguiente nivel de profundidad. Y así sucesivamente, ahondando todo lo necesario hasta que no quede un entero sin imprimir.

def imprimir(lista):
    for elemento in lista:
        if isinstance(elemento, list):
            imprimir(elemento)
        else:
            print(elemento)

lista_anidada = [1, [2, [3, 4, [5, 6], 7]], 8, [9, 10]]

imprimir(lista_anidada)

>>> 
1
2
3
4
5
6
7
8
9
10

No se salva ni el apuntador.

Javier Montero Gabarró


http://elclubdelautodidacta.es/wp/2013/02/python-una-funcion-para-iterar-sobre-listas-anidadas/


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python: Iterando y modificando una lista

Objetivo: presentar técnicas de iteración sobre listas que permitan su modificación in-situ.

Comencemos planteando el siguiente problema: tenemos una lista de números y queremos modificarla de modo que almacene, en lugar del número en sí, su cuadrado.

No parece un problema complejo que no sepamos resolver con lo que ya conocemos:

lista = [1, 2, 3, 4, 5]

for elemento in lista:
    elemento = elemento * elemento
    print(elemento) # Una simple verificación

Si ahora observamos la salida:

>>> 
1
4
9
16
25

Aparenta todo haber ido de maravilla; sin embargo, nos encontramos con un pequeño detalle importante:

>>> lista
[1, 2, 3, 4, 5]

¡La lista no ha cambiado!

¿Cómo es posible? ¿Acaso no hemos comprobado como elemento, que ha pasado sucesivamente por todos y cada uno de los elementos de la lista, ha actualizado su valor a elemento * elemento?

Así es. Pero, lo que sucede, es que elemento no es más que una copia del valor original. Por muchos cambios que efectuemos sobre él no afectaran a la lista.

Necesitamos cambiar de estrategia.

Una forma de salvar el escollo sería reconstruir una lista temporal con los nuevos valores al cuadrado.

lista = [1, 2, 3, 4, 5]

lista_temp = []

for elemento in lista:
    elemento = elemento * elemento
    print(elemento)
    lista_temp.append(elemento)

La lista temporal, lista_temp es, en principio, una lista en blanco. Con cada iteración le agregamos el nuevo valor de elemento mediante el método append().

lista_temp ya tiene la solución buscada:

>>> lista
[1, 2, 3, 4, 5]
>>> lista_temp
[1, 4, 9, 16, 25]

Si lo necesitamos, podemos reasignar hacia dónde apunta lista:

>>> lista = lista_temp
>>> lista
[1, 4, 9, 16, 25]

En un nivel mayor de elegancia (en Python, la elegancia suele coincidir con el menor número de líneas de código), está la siguiente técnica: recorriendo el bucle for a través de el índice de la lista en vez de sus elementos. Es decir, como si fuera un bucle for típico de cualquier otro lenguaje de programación:

lista = [1, 2, 3, 4, 5]

for indice in range(len(lista)):
    lista[indice] = lista[indice] * lista[indice]
    print(lista[indice])

El resultado es el que esperamos:

>>> 
1
4
9
16
25
>>> lista
[1, 4, 9, 16, 25]

No solo la impresión es correcta, sino que la lista ha sido actualizada in-situ. Desde el bucle for, a través del índice, hemos podido acceder directamente a la lista, no a una copia de ella.

Apréndete bien esta técnica, pues es muy común. Itera sobre los elementos o los índices según tu necesidad concreta.

Date cuenta de que, para poder iterar a través de los índices, necesitamos conocer el número de elementos totales de la lista. Esto lo obtenemos con la expresión len(lista). Recuerda que la función len() nos devuelve el total de elementos de una secuencia. A continuación la función range() nos construye un iterable: range(len(lista)) facilitará al bucle for, sucesivamente, todos los valores enteros desde cero hasta uno menos que la longitud de la lista, precisamente el mismo rango que tienen sus índices.

Existe un nivel mayor de elegancia aún, las listas por comprensión, una de las construcciones más fascinantes y poderosas de Python. Son merecedoras de uno o varios artículos por separado, pero no me gustaría despedirme hoy sin que, al menos, y a modo de aperitivo, compruebes su potencia:

lista = [1, 2, 3, 4, 5]
lista = [elemento * elemento for elemento in lista]

Así de elegante:

>>> lista
[1, 4, 9, 16, 25]

Bienvenido a Python.

Javier Montero Gabarró


Python: Iterando y modificando una lista


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.