Apéndice I — Listas.

Objetivo. Describir la estructura de datos list mediante la exposición de ejemplos.

I.1 Introducción

En Python se tienen cuatro tipos de estructuras de datos básicas, también conocidas como colecciones. Cuando se selecciona un tipo de colección, es importante conocer sus propiedades para incrementar la eficiencia y/o la seguridad de los datos. La siguiente tabla resume estos cuatro tipos:

Tipo Ordenada Inmutable Indexable Duplicidad
List SI NO SI SI
Tuple SI SI SI SI
Sets NO NO NO NO
Dict NO NO SI NO

I.2 Listas

  • Consisten en una secuencia ordenada y mutable de elementos.
    • Ordenadas significa que cada elemento dentro de la lista está indexado y mantiene su orden definido en su creación.
    • Mutable significa que los elementos de la lista se pueden modificar, y además que se pueden agregar o eliminar elementos.
  • Las listas pueden tener elementos duplicados, es decir, elementos del mismo tipo y con el mismo contenido.
  • Las listas se definen usando corchetes ([]) y comas (,) para separar sus elementos.
Ejemplo I.1: Creación de listas.

Vamos a crear las siguientes listas:

  • gatos : Razas de gatos.
  • origen : Origen de cada raza de gatos.
  • pelo_largo: Si tienen pelo largo o no.
  • pelo_corto: Si tienen pelo corto o no.
  • peso_minimo: El peso mínimo que pueden tener.
  • peso_maximo: El peso máximo que pueden tener.
# Listas con elementos de tipo cadena (str)
gatos = ['Persa', 'Sphynx', 'Ragdoll','Siamés']
origen = ['Irán', 'Toronto', 'California', 'Tailandia']

# Listas con elementos de tipo lógico (bool)
pelo_largo = [True, False, True, False]
pelo_corto = [False, False, False, True]

# Listas con elementos de tipo flotante
peso_minimo = [2.3, 3.5, 5.4, 2.5]
peso_maximo = [6.8, 7.0, 9.1, 4.5]
# Revisamos el tipo y contenido de cada lista
print(type(gatos), gatos)
print(type(origen), origen)
print(type(pelo_largo), pelo_largo)
print(type(pelo_corto), pelo_corto)
print(type(peso_minimo), peso_minimo)
print(type(peso_maximo), peso_maximo)
<class 'list'> ['Persa', 'Sphynx', 'Ragdoll', 'Siamés']
<class 'list'> ['Irán', 'Toronto', 'California', 'Tailandia']
<class 'list'> [True, False, True, False]
<class 'list'> [False, False, False, True]
<class 'list'> [2.3, 3.5, 5.4, 2.5]
<class 'list'> [6.8, 7.0, 9.1, 4.5]

Observaciones:

  • Cada lista contiene 4 elementos.
  • Los elementos de cada lista son del mismo tipo. No obstante, es posible que las listas tengan elementos de tipos distintos.
  • Los elementos son cadenas, flotantes y tipos lógicos.

I.3 Indexado

Se puede acceder a cada elemento de las listas de manera similar a como se hace con las cadenas, veáse el tema Indexación de las cadenas en la Sección E.3.

Por ejemplo:

gatos[0] # Primer elemento
'Persa'
gatos[1:4] # Todos los elementos, desde el 1 hasta el 3
['Sphynx', 'Ragdoll', 'Siamés']
gatos[-1] # Último elemento
'Siamés'
gatos[::2] # Todos los elementos en saltos de dos en dos
['Persa', 'Ragdoll']
gatos[::-1] # Todos los elementos en reversa
['Siamés', 'Ragdoll', 'Sphynx', 'Persa']

Para conocer el contenido y tipo de alguno de los elementos podemos hacer lo siguiente:

print(type(gatos[0]), gatos[0])
print(type(pelo_largo[2]), pelo_largo[2])
print(type(peso_maximo[-1]), peso_maximo[-1])
<class 'str'> Persa
<class 'bool'> True
<class 'float'> 4.5

I.4 Mutabilidad de las listas.

Usando el indexado es posible cambiar el contenido de los elementos de una lista.

Veamos los siguientes ejemplos:

  • Modificamos el elemento 1 de la lista peso_maximo.
print('Original  :', peso_maximo) # Antes
peso_maximo[1] = 100.5 
print('Modificado:', peso_maximo) # Después
Original  : [6.8, 7.0, 9.1, 4.5]
Modificado: [6.8, 100.5, 9.1, 4.5]
  • El tipo de los elementos puede ser diferente del original.
print('Original  :', peso_maximo) # Antes
peso_maximo[1] = "una cadena" 
print('Modificado:', peso_maximo) # Después
Original  : [6.8, 100.5, 9.1, 4.5]
Modificado: [6.8, 'una cadena', 9.1, 4.5]
  • Incluso es posible agegar otra lista en uno de los elementos:
print('Original  :', peso_maximo) # Antes
peso_maximo[1] = [1,2,'a','b','c'] 
print('Modificado:', peso_maximo) # Después
Original  : [6.8, 'una cadena', 9.1, 4.5]
Modificado: [6.8, [1, 2, 'a', 'b', 'c'], 9.1, 4.5]
  • Se pueden usar rangos para modificar varios elementos a la vez.
print('Original  :', peso_maximo) # Antes
peso_maximo[1:3] = ['a', 'b'] 
print('Modificado:', peso_maximo) # Después
Original  : [6.8, [1, 2, 'a', 'b', 'c'], 9.1, 4.5]
Modificado: [6.8, 'a', 'b', 4.5]
  • Se pueden modificar elementos en saltos diferentes a uno.
print('Original  :', peso_maximo) # Antes
peso_maximo[::2] = [0.0, 0.0] 
print('Modificado:', peso_maximo) # Después
Original  : [6.8, 'a', 'b', 4.5]
Modificado: [0.0, 'a', 0.0, 4.5]
# Regresamos a los valores originales de la lista peso_maximo
peso_maximo[0:3] = [6.8, 7.0, 9.1]
print(peso_maximo)
[6.8, 7.0, 9.1, 4.5]

I.5 Nombres e identificadores

Es importante entender la forma en como están almacenados los elementos de la lista en memoria. Veamos el siguiente ejemplo.

Generamos otro nombre para la lista (no se crea una copia):

p_min = peso_minimo

Verificamos que sea la misma lista:

print(id(p_min), p_min)
print(id(peso_minimo), peso_minimo)
2064603102080 [2.3, 3.5, 5.4, 2.5]
2064603102080 [2.3, 3.5, 5.4, 2.5]

Podemos acceder a un elemento de la lista usando el indexado y ver su contenido y su identificador en memoria. Por ejemplo, para ver los elementos con índices 2 y 3 de la lista p_min hacemos lo siguiente:

print(id(p_min[2]), p_min[2])
print(id(p_min[3]), p_min[3])
2064598458256 5.4
2064602246416 2.5

Hacemos lo mismo usando el nombre peso_minimo:

print(id(peso_minimo[2]), peso_minimo[2])
print(id(peso_minimo[3]), peso_minimo[3])
2064598458256 5.4
2064602246416 2.5

Observa que la lista tiene su propio identificador y cada elemento de la lista tiene un identificador diferente.

Eliminamos el nombre peso_minimo:

del peso_minimo 

Ya no existe el nombre peso_minimo por lo que la siguiente declaración dará un error:

print(peso_minimo) 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 print(peso_minimo) 

NameError: name 'peso_minimo' is not defined

Pero aún se puede acceder a la lista original a través del nombre p_min:

print(id(p_min), p_min) 
2064603102080 [2.3, 3.5, 5.4, 2.5]

Se puede eliminar un elemento de la lista:

del p_min[2] 

Veamos el estado actual de la lista:

print(id(p_min), p_min) 
2064603102080 [2.3, 3.5, 2.5]

I.6 Funciones incorporadas que operan sobre las listas.

Existen muchas operaciones que se pueden realizar sobre las listas usando funciones incorporadas (built-in functions).

I.6.1 len(): determina la longitud de la lista.

len(gatos) 
4

I.6.2 max(): determina el máximo elemento de la lista.

max(peso_maximo) 
9.1

I.6.3 min(): determina el mínimo elemento de la lista.

min(peso_maximo) 
4.5

I.6.4 any(): revisa si ALGÚN elemento es True.

any(pelo_largo) 
True

I.6.5 all(): revisa si TODOS los elementos son True.

all(pelo_corto)
False

I.6.6 sorted(): ordena los elementos de la lista.

print(peso_maximo) # Lista original antes del ordenamiento
sorted(peso_maximo) 
[6.8, 7.0, 9.1, 4.5]
[4.5, 6.8, 7.0, 9.1]

La función sorted() genera una nueva lista ordenada, pero no modifica la lista original. Si se imprime la lista peso_maximo veremos que no ha cambiado:

print(peso_maximo)
[6.8, 7.0, 9.1, 4.5]

I.6.7 sum(): suma los elementos de la lista.

# El operador suma debe estar definido para el tipo de los elementos de la lista
sum(peso_maximo) 
27.4

I.6.8 enumerate(): enumera los elementos de una lista.

# Genera tuplas del tipo `(i, elemento)`.
list(enumerate(origen)) 
[(0, 'Irán'), (1, 'Toronto'), (2, 'California'), (3, 'Tailandia')]

Observa que para el caso de enumerate() el resultado se debe convertir a un tipo que se pueda desplegar, en este caso se convierte a una lista usando list().

I.6.9 zip(): agrupa los elementos de dos listas.

# Genera tuplas del tipo `(gatos[i], origen[i])`.
list(zip(gatos, origen)) 
[('Persa', 'Irán'),
 ('Sphynx', 'Toronto'),
 ('Ragdoll', 'California'),
 ('Siamés', 'Tailandia')]

Observa que para el caso de zip() el resultado se debe convertir a un tipo que se pueda desplegar, en este caso se convierte a una lista usando list().

I.7 Operadores sobre listas

Se pueden usar los operadores +, * e in sobre las listas.

I.7.1 +: concatena listas.

gatos + peso_maximo # Concatenación de dos listas
['Persa', 'Sphynx', 'Ragdoll', 'Siamés', 6.8, 7.0, 9.1, 4.5]

I.7.2 *: hace réplicas de las listas.

origen * 3 # Duplicación de la lista
['Irán',
 'Toronto',
 'California',
 'Tailandia',
 'Irán',
 'Toronto',
 'California',
 'Tailandia',
 'Irán',
 'Toronto',
 'California',
 'Tailandia']

I.7.3 in: revisa si un elemento está en la lista.

'Siamés' in gatos # ¿Está el elemento `Siamés` en la lista gatos?
True

I.8 Métodos de las listas.

En términos de Programación Orientada a Objetos, la clase <class 'list'> define una serie de métodos que se pueden aplicar sobre los objetos del tipo list. Veamos algunos ejemplos:

print(gatos) # Imprimimos la lista original
['Persa', 'Sphynx', 'Ragdoll', 'Siamés']

I.8.1 Agregar elementos a la lista.

gatos.append('Siberiano') # Se agrega un elemento al final de la lista
print(gatos)
['Persa', 'Sphynx', 'Ragdoll', 'Siamés', 'Siberiano']
gatos.append('Persa') # Se agrega otro elemento al final de la lista, repetido
print(gatos)
['Persa', 'Sphynx', 'Ragdoll', 'Siamés', 'Siberiano', 'Persa']

I.8.2 Eliminar elementos de la lista.

gatos.remove('Persa') 
print(gatos) 
['Sphynx', 'Ragdoll', 'Siamés', 'Siberiano', 'Persa']

Observa que solo se elimina el primer elemento Persa que encuentra.

I.8.3 Insertar elementos en la lista.

Podemos insertar un elemento en un lugar específico de la lista:

gatos.insert(0,'Persa')
print(gatos)
['Persa', 'Sphynx', 'Ragdoll', 'Siamés', 'Siberiano', 'Persa']

I.8.4 Extraer el último elemento de la lista.

También es posible extraer el último elemento de la lista

gatos.pop() 
'Persa'
print(gatos)
['Persa', 'Sphynx', 'Ragdoll', 'Siamés', 'Siberiano']

I.8.5 Ordenar la lista.

gatos.sort() 
print(gatos)
['Persa', 'Ragdoll', 'Siamés', 'Siberiano', 'Sphynx']

I.8.6 La lista con los elementos en reversa.

gatos.reverse() 
print(gatos)
['Sphynx', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

I.8.7 Concatenar dos listas.

pelo_corto.extend(pelo_largo) 
print(pelo_corto)
[False, False, False, True, True, False, True, False]

I.8.8 Contar los elementos de un tipo.

pelo_corto.count(False) # Contar los elementos que tengan el valor 'False'
5
pelo_corto.count(True) # Contar los elementos que tengan el valor 'True'
3
Ejemplo I.2: Recuperando una lista.

Considera la lista original peso_minimo = [2.3, 3.5, 5.4, 2.5]. El nombre fue eliminado en un ejemplo anterior. Vamos a recuperar la lista a través del nombre p_min que aún existe:

Veamos el identificador y contenido de p_min

print(id(p_min), p_min)
2064603102080 [2.3, 3.5, 2.5]

Necesitamos insertar el valor 5.4 en el lugar 2 de la lista:

p_min.insert(2, 5.4)

Revisamos el identificador y contenido de p_min

print(id(p_min), p_min)
2064603102080 [2.3, 3.5, 5.4, 2.5]

Recuperamos el nombre original:

peso_minimo = p_min

Revisamos el identificador y contenido de peso_minimo

print(id(peso_minimo), peso_minimo)
2064603102080 [2.3, 3.5, 5.4, 2.5]

Eliminamos p_min

del p_min

Una descripción detallada de los métodos de la listas se puede ver en More on Lists

I.9 Copiando listas

  • Una lista es un objeto que contiene varios elementos.
  • Para crear una copia de una lista, se debe generar un espacio de memoria en donde se copien todos los elementos de la lista original y asignar un nuevo nombre para esta nueva lista.
  • Lo anterior no se puede hacer simplemente con el operador de asignación =.

Veamos un ejemplo:

gatitos = gatos
print(gatos)
print(gatitos)
['Sphynx', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
['Sphynx', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Podemos observar que al imprimir la lista mediante los nombres gatos y gatitos obtenemos el mismo resultado. Ahora, modifiquemos el primer elemento usando el nombre gatitos:

gatitos[0] = 'Singapur'
print(gatos)
print(gatitos)
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Observamos que al imprimir la lista usando gatos y gatitos volvemos a obtener el mismo resultado. Lo anterior significa que el operador de asignación solamente creó un nuevo nombre para el mismo objeto en memoria, por lo que en realidad no hizo una copia de la lista. Lo anterior lo podemos verificar usando la función id() para ver la dirección en memoria del objeto:

print(id(gatitos))
print(id(gatos))
2339996503872
2339996503872

I.9.1 Copiando con [:]

Para crear una nueva lista copiando todos los elementos podemos hacer lo siguiente:

gatitos = gatos[:]
print(type(gatitos))
print(id(gatitos))
print(gatitos)

print(type(gatos))
print(id(gatos))
print(gatos)
<class 'list'>
2340003231168
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
<class 'list'>
2339996503872
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Observa que el identificador en memoria de cada lista es diferente por lo que se trata de listas diferentes.

I.9.2 Copiando con el método copy()

La clase <class 'list'> contiene un método llamado copy() que efectivamente realiza una copia de la lista:

gatitos = gatos.copy()
print(type(gatitos))
print(id(gatitos))
print(gatitos)

print(type(gatos))
print(id(gatos))
print(gatos)
<class 'list'>
2340003240960
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
<class 'list'>
2339996503872
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Observa que el identificador en memoria de cada lista es diferente por lo que se trata de listas diferentes.

I.9.3 Copiando con el constructor

La función list() transforma un objeto iterable en una lista. La podemos usar para copiar una lista como sigue:

gatitos = list(gatos)
print(type(gatitos))
print(id(gatitos))
print(gatitos)

print(type(gatos))
print(id(gatos))
print(gatos)
<class 'list'>
2340003294272
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
<class 'list'>
2339996503872
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Observa que el identificador en memoria de cada lista es diferente por lo que se trata de listas diferentes.

NotaNota.

Lo que sucede en este último caso, es que se ejecuta el constructor de la clase <class 'list'>, el cual recibe un objeto iterable (lista, tupla, diccionario, entre otros), copia todos los elementos de ese iterable y los pone en una lista que se almacena en un espacio en memoria diferente al iterable original. Esto lo realiza el constructor de la clase list y es una característica de la Programación Orientada a Objetos.

I.9.4 Copiando con la biblioteca copy

La biblioteca copy de Python ofrece herramientas para copiado de objetos. Veamos un ejemplo:

# Importamos la biblioteca copy
import copy

# Usamos la función copy()
gatitos = copy.copy(gatos)
print(type(gatitos))
print(id(gatitos))
print(gatitos)

print(type(gatos))
print(id(gatos))
print(gatos)
<class 'list'>
2340003010880
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']
<class 'list'>
2339996503872
['Singapur', 'Siberiano', 'Siamés', 'Ragdoll', 'Persa']

Observa que el identificador en memoria de cada lista es diferente por lo que se trata de listas diferentes.

Más información sobre el uso de esta biblioteca se puede ver en Shallow and deep copy operations

I.10 Listas mas complejas.

Las listas pueden contener cualquier tipo de dato de Python e incluso tipos construidos por el usuario. Veamos el siguiente ejemplo:

lst = ['a', 'b', 'c', [1, 2, 3], 5+1j, 'lista muy compleja']

Observa que: * los primeros tres elementos, lst[0], lst[1] y lst[2] son de tipo str, * el cuarto elemento, lst[3], es una lista, * el quinto elemento, lst[4], es un número complejo, * y el último elemento, lst[5], es una cadena.

Podemos usar los nombres lst[i], con i =0,1,2,3,4,5, como nombres de variables.

El contenido y tipo de cada elemento se puede conocer usando esos nombres:

print(lst[0], type(lst[0]))
print(lst[1], type(lst[1]))
print(lst[2], type(lst[2]))
print(lst[3], type(lst[3]))
print(lst[4], type(lst[4]))
print(lst[5], type(lst[5]))
a <class 'str'>
b <class 'str'>
c <class 'str'>
[1, 2, 3] <class 'list'>
(5+1j) <class 'complex'>
lista muy compleja <class 'str'>

Podemos acceder a los elementos individuales de los objetos que son más complejos. Por ejemplo, para lst[3] y lst[5] que son una lista y una cadena respectivamente, hacemos lo siguiente:

# Segundo elemento de la lista 'lst[3]'
print(lst[3][1], type(lst[3][1]))

# Cuarto elemento de la cadena 'lst[5]'
print(lst[5][3], type(lst[5][3]))
2 <class 'int'>
t <class 'str'>

Otro ejemplo:

superlista = ['México', 3.141592, 20, 1j, [1,2,3,'lista']]
superlista
['México', 3.141592, 20, 1j, [1, 2, 3, 'lista']]
superlista[0] # El elemento 0 de la lista
'México'
superlista[4] # El elemento 4 de la lista (este elemento es otra lista)
[1, 2, 3, 'lista']
superlista[4][2] # El elemento 2 del elemento 4 de la lista original
3