AulaML Python · C++
Python principiante · 40 min · Estructuras de datos

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.

Lección 2 de 4 del módulo
Al terminar esta lección podrás
  • 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
  1. ¿Qué es una lista y qué es una tupla?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis y operaciones básicas
  5. Ejemplo básico: una lista que crece y decrece
  6. Ejemplo intermedio: ordenar sin destruir el original
  7. Caso práctico: inventario de una tienda
  8. Visualización: alias, copia y orden
  9. Errores comunes
  10. Buenas prácticas
  11. Ejercicios
  12. Soluciones explicadas
  13. Resumen
  14. 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ónEjemploCosteMotivo
Acceso por índicelista[5]O(1)Salto directo a la posición
Añadir al finallista.append(x)O(1)*Amortizado; a veces se realoca
Insertar al iniciolista.insert(0, x)O(n)Desplaza todos los elementos
Quitar el últimolista.pop()O(1)No hay que desplazar nada
Quitar el primerolista.pop(0)O(n)Desplaza el resto una posición
Quitar por valorlista.remove(x)O(n)Busca y luego desplaza
Buscar pertenenciax in listaO(n)Recorre hasta encontrar
Ordenarlista.sort()O(n log n)Comparaciones y movimientos

Sintaxis y operaciones básicas

PYTHON crear y modificar
# 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) coloca x en la posición i y desplaza lo que estaba a la derecha.
  • pop() devuelve y elimina el último; pop(i) hace lo mismo con la posición i.
  • remove(x) elimina la primera aparición del valor; si no existe, lanza ValueError.
  • sort() ordena en su sitio y devuelve None; sorted() devuelve una lista nueva.

Ejemplo básico: una lista que crece y decrece

PYTHON frutas.py
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.

PYTHON precios.py
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.

PYTHON inventario.py
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?

Ejecución visual ·Una lista, dos nombres
1 / 6
1 notas = [15, 8, 20]
2 copia = notas
3 ordenadas = sorted(notas)
4 copia.append(12)
5 notas.sort(reverse=True)
6 print(notas, ordenadas)
Se crea una lista con tres enteros y notas apunta a ella.
Variables
notas = [15, 8, 20]
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

Errores comunes

Mensaje o síntomaPor qué ocurreCómo se arregla
IndexError: list index out of rangeLa posición no existeComprueba len(lista) antes de acceder
ValueError: list.remove(x): x not in listEl valor no está en la listaVerifica con if x in lista o usa pop(i)
AttributeError: 'tuple' object has no attribute 'append'Intentaste modificar una tuplaUsa una lista o crea una tupla nueva
TypeError: 'tuple' object does not support item assignmentAsignaste una posición de una tuplaConvierte con list(t) si necesitas cambios
La lista original cambia sin quererHay un alias, no una copianueva = original[:] o original.copy()
Alias involuntario
Incorrecto
PYTHON Las dos variables cambian
original = [1, 2, 3]
copia = original
copia.append(4)
print(original)   # [1, 2, 3, 4]
Correcto
PYTHON Solo cambia la copia
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 append al final; si necesitas insertar mucho al principio, considera otra estructura.
  • Comprueba con in antes de remove si 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.
Lista frente a std::vector
Python
PYTHON lista.py
ventas = [120, 90, 150]
ventas.append(200)
ventas.sort()
print(ventas[0], len(ventas))
C++
CPP vector.cpp
#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

Ejercicio 1

Lista de compras

Crea una lista vacía llamada 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.

Completar código Completa las operaciones
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.

Ejercicio 2

Promedio sin extremos

Dada 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.
  • append añade al final, insert en una posición y pop extrae; remove elimina por valor.
  • sort() ordena en su sitio y devuelve None; sorted() devuelve una lista nueva.
  • b = a crea 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 = punto y sirven para registros fijos.
  • El acceso por índice y el append son baratos; insertar al inicio y buscar son O(n).

Evaluación

Evaluación 1

Después de a = [1, 2], b = a y b.append(3), ¿qué vale a?

Evaluación 2

¿Qué devuelve sorted([3, 1, 2]) y qué deja en la lista original?

Evaluación 3

¿Cuál de estas operaciones sobre una lista de 10 000 elementos es más cara?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.