Listas y tuplas
Guardar colecciones ordenadas, modificarlas sin sorpresas y distinguir cuándo conviene que no cambien
Las listas agrupan elementos que pueden cambiar; las tuplas agrupan elementos que no. Aprenderás a añadir, quitar y ordenar sin caer en el alias involuntario, a copiar de verdad y a elegir la estructura adecuada según el problema.
- Crear listas y tuplas y distinguir cuándo usar cada una
- Añadir, insertar, eliminar y ordenar elementos con el coste adecuado
- Explicar la diferencia entre copiar una lista y crear un alias
- Desempaquetar tuplas y acceder a listas anidadas
- Elegir operaciones eficientes según el tamaño de los datos
Antes necesitas: Strings, índices y slicing · Variables y tipos de datos
En esta lección
- ¿Qué es una lista y qué es una tupla?
- ¿Para qué sirve?
- ¿Cómo funciona por dentro?
- Sintaxis y operaciones básicas
- Ejemplo básico: una lista que crece y decrece
- Ejemplo intermedio: ordenar sin destruir el original
- Caso práctico: inventario de una tienda
- Visualización: alias, copia y orden
- Errores comunes
- Buenas prácticas
- Ejercicios
- Soluciones explicadas
- Resumen
- Evaluación
Una variable guarda un valor. ¿Qué haces cuando necesitas guardar veinte? Podrías crear nota1, nota2, nota3… y el programa se volvería imposible de mantener. Las colecciones resuelven ese problema: un solo nombre contiene muchos valores y puedes recorrerlos, contarlos o modificarlos con las mismas operaciones.
Las dos colecciones ordenadas básicas de Python son la lista y la tupla. Se parecen en la sintaxis y en la forma de indexarlas, pero se diferencian en algo decisivo: la lista se puede modificar y la tupla no. Elegir bien entre ambas evita errores y comunica intención a quien lee tu código.
¿Qué es una lista y qué es una tupla?
Una lista es una secuencia ordenada y mutable de elementos. Se escribe entre corchetes: notas = [15, 8, 20]. Puede contener cualquier tipo, incluso tipos mezclados, y su longitud cambia cuando añades o quitas elementos. El orden importa: la posición 0 es la primera y la última se puede alcanzar con -1.
Una tupla es una secuencia ordenada e inmutable. Se escribe con paréntesis: punto = (3, 5). Una vez creada, no puedes añadir, quitar ni reasignar posiciones. Esa rigidez es una ventaja cuando representas un registro fijo, como una coordenada, una fecha o una fila que no debe alterarse. Una tupla de un solo elemento necesita una coma final: (5,).
¿Para qué sirve?
- Guardar series de datos: las notas de un curso, los precios de una compra o las temperaturas de la semana.
- Mantener orden: cuando la posición significa algo, como el orden de llegada de los pedidos.
- Representar registros: una tupla
(nombre, edad, ciudad)describe una fila sin riesgo de modificar sus campos por accidente. - Recorrer y acumular: sumar ventas, filtrar productos con stock bajo o buscar el máximo de una lista.
- Preparar datos para ML: un vector de características suele ser una lista de números antes de convertirse en un array de NumPy.
¿Cómo funciona por dentro?
Una lista no guarda los objetos dentro de sí misma: guarda referencias a ellos. Cuando escribes a = [1, 2, 3] y luego b = a, no se copia nada. a y b apuntan al mismo objeto en memoria. Si modificas la lista desde b, el cambio se ve desde a, porque solo hay una lista. A esto se le llama alias, y es la fuente número uno de errores con colecciones.
Detrás de una lista hay un bloque contiguo de referencias con una capacidad mayor o igual que su longitud. Cuando haces append y aún queda capacidad, el elemento se coloca al final sin mover nada; si se agota, Python reserva un bloque más grande y copia las referencias. Por eso append es O(1) amortizado: casi siempre barato, ocasionalmente una copia.
Las demás operaciones no son igual de baratas. Insertar al principio obliga a desplazar todos los elementos una posición; eliminar por valor exige buscar primero; comprobar si un elemento existe recorre la lista hasta encontrarlo. En cambio, acceder por índice es inmediato. Esta tabla te sirve para decidir antes de escribir el bucle:
| Operación | Ejemplo | Coste | Motivo |
|---|---|---|---|
| Acceso por índice | lista[5] | O(1) | Salto directo a la posición |
| Añadir al final | lista.append(x) | O(1)* | Amortizado; a veces se realoca |
| Insertar al inicio | lista.insert(0, x) | O(n) | Desplaza todos los elementos |
| Quitar el último | lista.pop() | O(1) | No hay que desplazar nada |
| Quitar el primero | lista.pop(0) | O(n) | Desplaza el resto una posición |
| Quitar por valor | lista.remove(x) | O(n) | Busca y luego desplaza |
| Buscar pertenencia | x in lista | O(n) | Recorre hasta encontrar |
| Ordenar | lista.sort() | O(n log n) | Comparaciones y movimientos |
Sintaxis y operaciones básicas
# Listas: corchetes y elementos separados por comas
notas = [15, 8, 20]
mixta = ["Ada", 36, True]
# Tuplas: paréntesis; la coma es obligatoria en las de un elemento
punto = (3, 5)
sola = (7,)
# Operaciones que cambian la lista en su sitio
notas.append(12) # añade al final
notas.insert(1, 18) # inserta en la posición 1
notas.remove(8) # elimina el primer 8
ultimo = notas.pop() # saca y devuelve el último
print(notas)
print(punto[0], punto[1])append(x)añade un elemento al final; si le pasas una lista, la añade como un elemento anidado.extend(otra)añade todos los elementos de otra colección, uno por uno.insert(i, x)colocaxen la posicióniy desplaza lo que estaba a la derecha.pop()devuelve y elimina el último;pop(i)hace lo mismo con la posicióni.remove(x)elimina la primera aparición del valor; si no existe, lanzaValueError.sort()ordena en su sitio y devuelveNone;sorted()devuelve una lista nueva.
Ejemplo básico: una lista que crece y decrece
frutas = ["manzana", "pera"]
frutas.append("uva")
frutas.insert(0, "kiwi")
ultima = frutas.pop()
print(frutas)
print(f"Quité {ultima} y quedan {len(frutas)}")La lista empieza con dos elementos. append("uva") la deja en ["manzana", "pera", "uva"]. insert(0, "kiwi") coloca el kiwi al principio y desplaza los otros tres, así que el orden pasa a ser kiwi, manzana, pera, uva. pop() extrae el último elemento, "uva", y lo guarda en ultima; la lista queda con tres elementos.
La quinta línea imprime la lista completa, con corchetes y comillas porque print muestra la representación interna. La sexta usa una f-string: ultima vale "uva" y len(frutas) vale 3. Observa que append e insert no devuelven nada útil; actúan sobre la lista y su efecto se ve al consultarla después.
Ejemplo intermedio: ordenar sin destruir el original
Ordenar es una de las operaciones más frecuentes, y también una de las que más confusión genera por la diferencia entre sort() y sorted(). El primero modifica la lista y devuelve None; el segundo deja intacta la original y entrega una lista nueva.
precios = [120, 80, 45, 200]
ascendente = sorted(precios)
precios.sort(reverse=True)
copia = precios[:]
copia.append(30)
print(ascendente)
print(precios)
print(copia)
print(precios is copia)sorted(precios) devuelve [45, 80, 120, 200] sin tocar el original. precios.sort(reverse=True) sí lo reordena, de mayor a menor: [200, 120, 80, 45]. La línea copia = precios[:] crea una lista nueva con los mismos elementos; el slice completo es la forma clásica de copiar, aunque precios.copy() o list(precios) hacen lo mismo.
Al añadir 30 a la copia, la original no cambia. La última línea comprueba identidad con is: devuelve False porque son dos objetos distintos. Si hubieras escrito copia = precios, el append habría afectado también a precios y is habría dado True. Esa diferencia entre igualdad (==, mismos valores) e identidad (is, mismo objeto) se vuelve crítica al pasar listas a funciones.
Caso práctico: inventario de una tienda
Un inventario sencillo puede ser una lista de listas: cada producto es [nombre, stock]. La lista exterior guarda los productos y la interior guarda los dos campos. Para actualizar el stock se encadenan dos índices: el primero elige el producto, el segundo el campo.
inventario = [["teclado", 12], ["mouse", 30], ["monitor", 1]]
inventario[2][1] -= 1 # se vendió el único monitor
inventario.append(["webcam", 0])
reponer = []
for nombre, stock in inventario:
if stock <= 2:
reponer.append(nombre)
print(inventario[2])
print(f"Productos: {len(inventario)}")
print("Reponer:", reponer)inventario[2] es ["monitor", 1]; [1] accede al stock y -= 1 lo deja en cero. Después se añade una webcam sin unidades. El bucle recorre la lista y desempaqueta cada sublista en nombre y stock: es más legible que usar producto[0] y producto[1]. La condición detecta el monitor y la webcam, y append construye la lista de reposición. El resultado es ["monitor", 0], cuatro productos y ["monitor", "webcam"].
Visualización: alias, copia y orden
El siguiente programa reúne los tres conceptos que más se confunden. Antes de avanzar, predice qué se imprimirá: ¿la copia comparte los cambios? ¿sorted modifica la lista? ¿Qué pasa cuando se ordena después de añadir?
notas = [15, 8, 20]copia = notasordenadas = sorted(notas)copia.append(12)notas.sort(reverse=True)print(notas, ordenadas)notas apunta a ella.—
Avanza paso a paso: observa la línea resaltada, las variables y la salida.
Errores comunes
| Mensaje o síntoma | Por qué ocurre | Cómo se arregla |
|---|---|---|
IndexError: list index out of range | La posición no existe | Comprueba len(lista) antes de acceder |
ValueError: list.remove(x): x not in list | El valor no está en la lista | Verifica con if x in lista o usa pop(i) |
AttributeError: 'tuple' object has no attribute 'append' | Intentaste modificar una tupla | Usa una lista o crea una tupla nueva |
TypeError: 'tuple' object does not support item assignment | Asignaste una posición de una tupla | Convierte con list(t) si necesitas cambios |
| La lista original cambia sin querer | Hay un alias, no una copia | nueva = original[:] o original.copy() |
original = [1, 2, 3]
copia = original
copia.append(4)
print(original) # [1, 2, 3, 4]original = [1, 2, 3]
copia = original[:]
copia.append(4)
print(original) # [1, 2, 3]`copia = original` solo duplica la referencia. El slice `[:]` crea una lista nueva con los mismos elementos. Ojo: es una copia **superficial**; si los elementos fueran listas, las interiores seguirían compartidas.
Otro error silencioso es escribir notas = notas.sort(). Como sort() devuelve None, la variable deja de apuntar a la lista y pasa a valer None; el siguiente print(notas) o len(notas) fallará. La forma correcta es notas.sort() sin asignación, o notas = sorted(notas) si quieres una lista nueva.
Buenas prácticas
- Usa listas cuando el número de elementos cambia; usa tuplas para registros fijos que no deben alterarse.
- No modifiques una lista mientras la recorres: itera sobre una copia o construye una lista nueva.
- Prefiere
appendal final; si necesitas insertar mucho al principio, considera otra estructura. - Comprueba con
inantes deremovesi el valor puede no existir. - Usa nombres en plural para colecciones (
precios,nombres) y en singular para un elemento. - No encadenes más de dos niveles de listas anidadas sin una buena razón; suele ser señal de que necesitas un diccionario.
ventas = [120, 90, 150]
ventas.append(200)
ventas.sort()
print(ventas[0], len(ventas))#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> ventas{120, 90, 150};
ventas.push_back(200);
std::sort(ventas.begin(), ventas.end());
std::cout << ventas[0] << " " << ventas.size() << std::endl;
return 0;
}La lista de Python y el `std::vector` crecen al final con coste amortizado. Python ordena con un método del objeto; C++ usa un algoritmo de la STL sobre un rango. En C++ el tipo del elemento es fijo; en Python cabe cualquier objeto.
Ejercicios
Lista de compras
compra. Añade "pan" y "leche" con append, inserta "huevos" al principio y elimina "pan" con remove. Muestra la lista final y cuántos productos contiene.Pista
Una lista vacía se escribe []. El orden de las operaciones cambia el resultado; ejecuta y comprueba.
notas = [12, 18, 7] notas.(15) # añadir al final primera = notas.(0) # sacar la primera notas.() # ordenar de menor a mayor print(primera, notas)
Los tres son métodos de lista; el último no recibe argumentos si ordena de forma natural.
Promedio sin extremos
notas = [14, 6, 18, 20, 9], calcula el promedio descartando la nota más baja y la más alta. Ordena una copia para no alterar la lista original, elimina los extremos con slicing y divide la suma entre la cantidad de notas que quedan.Pista
Con la lista ordenada, [1:-1] excluye el primero y el último. La suma se puede hacer con un bucle o con sum.
Soluciones explicadas
En la lista de compras, lo importante no es el resultado sino el efecto de cada método. append siempre añade al final, insert elige posición y remove busca el valor desde el principio. Si el orden de las llamadas cambia, el resultado cambia: insertar huevos después de quitar el pan daría otra lista. Leer el estado tras cada operación es la mejor forma de comprobarlo.
En el promedio, el detalle clave es trabajar sobre sorted(notas) y no sobre notas.sort(). Si hubieras ordenado la original, el ejercicio seguiría dando el mismo número, pero habrías destruido el orden de entrada sin necesidad. El slice [1:-1] es válido incluso con pocos elementos, aunque con menos de tres notas devolvería una lista vacía y la división fallaría.
Resumen
- Las listas son ordenadas y mutables; las tuplas son ordenadas e inmutables.
appendañade al final,inserten una posición ypopextrae;removeelimina por valor.sort()ordena en su sitio y devuelveNone;sorted()devuelve una lista nueva.b = acrea un alias;b = a[:]crea una copia superficial.- Las listas anidadas se acceden con índices encadenados, como
matriz[fila][columna]. - Las tuplas se desempaquetan con
x, y = puntoy sirven para registros fijos. - El acceso por índice y el
appendson baratos; insertar al inicio y buscar son O(n).
Evaluación
Después de a = [1, 2], b = a y b.append(3), ¿qué vale a?
¿Qué devuelve sorted([3, 1, 2]) y qué deja en la lista original?
¿Cuál de estas operaciones sobre una lista de 10 000 elementos es más cara?
¿Ya lo entendiste?
Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.