Saltar a contenido

1.6 · Listas y tuplas

Objetivos. Al terminar este capítulo podrás: crear y recorrer listas; modificarlas, segmentarlas (rebanado) y filtrarlas con comprehensions; y usar tuplas cuando el dato no debe cambiar. Como ampliación guiada, aplicarás enumerate, zip, map y filter a colecciones.

Evidencia de logro. Procesarás una colección de datos (p. ej. notas de alumnos) con rebanado, comprehensions y funciones de secuencias, eligiendo entre lista y tupla según la necesidad.

Contexto y motivación

Las colecciones permiten trabajar con varios datos como una unidad: una lista de precios, un historial o una tabla. En IA es habitual trabajar con colecciones, por ejemplo, con las características de un modelo, las muestras de un dataset o los resultados de una consulta.

Python ofrece dos estructuras de secuencia fundamentales: la lista (mutable, se puede cambiar) y la tupla (inmutable, no se puede modificar después de crearla). Elegir entre ellas depende de si los datos deben cambiar.

Vocabulario

Término Significado
Lista Secuencia mutable ([...])
Tupla Secuencia inmutable ((...))
Índice Posición (empieza en 0; negativo cuenta desde el final)
Rebanado (slicing) Extraer una subsecuencia [inicio:fin:paso]
Mutabilidad Si el objeto se puede modificar después de crearlo
Comprehension Forma compacta de crear una colección a partir de otra
Iterable Objeto que se puede recorrer (list, tuple, str…)

Prerrequisitos

1.5 · Bucles (sabes recorrer) y manejo de variables.

1. Crear y acceder a listas

lista = [10, 20, 30, 40]

print(lista[0])     # primer elemento
print(lista[-1])    # último (índice negativo)
print(lista[1:3])   # de la posición 1 a la 2 (el 3 no entra)

Salida esperada:

10
40
[20, 30]

Los índices empiezan en 0. Los negativos cuentan desde el final: [-1] es el último, [-2] el penúltimo.

2. Modificar listas

Las listas son mutables: puedes cambiar, añadir y borrar elementos.

numeros = [1, 2, 3]
numeros.append(4)     # añadir al final
numeros[0] = 99       # cambiar un elemento
del numeros[1]        # borrar el índice 1
print(numeros)

Salida esperada:

[99, 3, 4]

Sigue el estado paso a paso: [1, 2, 3] → [1, 2, 3, 4] → [99, 2, 3, 4] → [99, 3, 4]. Fíjate en que del numeros[1] borra la posición 1 (el 2), no el valor 1.

Un método es una operación que pertenece al objeto y se escribe detrás de él con un punto: numeros.append(4). Los que usarás casi a diario: append(v) (añadir al final), insert(i, v) (insertar en la posición i), remove(v) (borrar la primera aparición del valor v), pop() (sacar y devolver el último), sort() (ordenar la propia lista) y reverse() (invertirla).

3. Rebanado (slicing)

a = [0, 1, 2, 3, 4, 5]

print(a[1:4])   # posiciones 1, 2, 3
print(a[:3])    # desde el inicio hasta 2
print(a[3:])    # desde 3 hasta el final
print(a[::-1])  # invertida
print(a[::2])   # cada 2 elementos

Salida esperada:

[1, 2, 3]
[0, 1, 2]
[3, 4, 5]
[5, 4, 3, 2, 1, 0]
[0, 2, 4]

La sintaxis es [inicio:fin:paso], con fin excluido. [::-1] invierte la lista y a[::2] selecciona un elemento de cada dos.

El slicing devuelve una COPIA

a[1:4] crea una lista nueva; no modifica a. Es útil para trabajar con subconjuntos sin tocar el original.

4. Métodos y funciones útiles

numeros = [3, 1, 4, 1, 5]

print(numeros.count(1))                  # cuántas veces aparece el 1
print(numeros.index(4))                  # posición del valor 4

numeros.sort()                           # ordena la propia lista
print(numeros)
print(sorted(numeros, reverse=True))     # devuelve otra lista ordenada

print(len(numeros), sum(numeros), min(numeros), max(numeros))

Salida esperada:

2
2
[1, 1, 3, 4, 5]
[5, 4, 3, 1, 1]
5 14 1 5

Cómo leer la salida: el 1 aparece 2 veces; el 4 está en la posición 2; tras sort() la propia lista queda ordenada; sorted(..., reverse=True) devuelve otra lista de mayor a menor; y la última línea muestra longitud, suma, mínimo y máximo.

Diferencia clave: numeros.sort() modifica la lista y devuelve None; sorted(numeros) devuelve una lista nueva sin tocar la original. Para no romper tus datos, prefiere sorted() salvo que necesites ordenar la propia lista.

Evita l como nombre

En muchos tutoriales verás listas llamadas l. Es mala idea: en muchas fuentes l se confunde con 1 y no dice qué contiene. Usa nombres como numeros, precios o notas.

4.1 list(...)

El constructor list(...) crea una lista desde cualquier iterable:

print(list(range(1, 5)))                  # range -> lista
print(list("hola"))                        # cadena -> lista de caracteres

Salida esperada:

[1, 2, 3, 4]
['h', 'o', 'l', 'a']

4.2 Ampliación: ordenar por un campo con key

Cuando cada elemento tiene varios datos, como una tupla (nombre, edad), puedes decir por cuál ordenar con el parámetro key:

personas = [("Ana", 20), ("Luis", 25), ("Marta", 18)]
print(sorted(personas, key=lambda p: p[1]))               # por edad (asc)
print(sorted(personas, key=lambda p: p[1], reverse=True)) # por edad (desc)
print(max(personas, key=lambda p: p[1]))                  # la persona de más edad

Salida esperada:

[('Marta', 18), ('Ana', 20), ('Luis', 25)]
[('Luis', 25), ('Ana', 20), ('Marta', 18)]
('Luis', 25)

lambda p: p[1] es una función breve que recibe un elemento (p, una tupla) y devuelve su posición 1 (la edad). sorted, min y max la aplican a cada elemento y comparan esos resultados. Las tuplas se explican en la sección 9 y las funciones lambda, en 1.8 · Funciones; de momento basta con leerla como «ordena por la edad».

5. Concatenar y repetir

a = [1, 2]
b = [3, 4]

print(a + b)       # concatena y crea una lista nueva
print([0] * 3)     # repite el elemento tres veces

Salida esperada:

[1, 2, 3, 4]
[0, 0, 0]

+ une listas (crea una nueva) y * repite. Sirven para inicializar listas ([0] * 10 genera 10 ceros).

6. Copias y referencias (¡importante!)

Cuando haces b = a, no copias la lista: b apunta al mismo objeto. Cambiar una cambia la otra:

a = [1, 2, 3]
b = a              # b referencia la misma lista
b.append(4)
print("a:", a)     # ¡a también cambió!

Salida esperada:

a: [1, 2, 3, 4]

Para hacer una copia independiente usa copy(), [:] o list():

a = [1, 2, 3]
c = a.copy()       # o a[:] o list(a)
c.append(5)
print("a:", a, "| c:", c)
print("¿mismo objeto?", a is c)

Salida esperada:

a: [1, 2, 3] | c: [1, 2, 3, 5]
¿mismo objeto? False

== vs is en listas

a == c compara contenido (True); a is c compara identidad (False). Recuerda: b = a referencia (comparte), c = a.copy() copia. Esta diferencia explica muchos errores difíciles de localizar, como una lista que parece cambiar por sí sola.

7. Listas anidadas (matrices)

Una lista puede contener otras listas:

m = [[1, 2], [3, 4]]
print(m[0][1])                    # fila 0, columna 1
print([sum(fila) for fila in m])  # suma de cada fila

Salida esperada:

2
[3, 7]

m[fila][columna] para acceder. Las listas anidadas modelan tablas, matrices y datos jerárquicos, y se recorren con bucles o comprehensions anidados.

8. List comprehensions

Una comprehension crea una lista en una línea, filtra y transforma a la vez:

cuadrados = [x ** 2 for x in range(5)]
print(cuadrados)

pares = [x for x in range(10) if x % 2 == 0]
print(pares)

Salida esperada:

[0, 1, 4, 9, 16]
[0, 2, 4, 6, 8]

Se lee como una frase: "el cuadrado de x, para cada x en range(5)" y, en el segundo, "si x es par". Es la forma idiomática de construir listas; cuando el bucle es corto y claro, la comprehension es más legible y rápida que el bucle equivalente.

Cuándo usar un bucle

Si la lógica es larga, un bucle normal es más legible. Una comprehension con 3 condiciones y 2 transformaciones se vuelve ilegible.

8.1 any y all

Con la misma sintaxis, pero sin corchetes, puedes comprobar una condición sobre toda la colección de una vez:

print(any(x > 3 for x in [1, 2, 3, 4]))    # ¿algún elemento es mayor que 3?
print(all(x > 0 for x in [1, 2, 3]))       # ¿todos son positivos?

Salida esperada:

True
True

any() devuelve True si al menos un elemento cumple la condición y all() si la cumplen todos. Son útiles para preguntas como «¿algún pedido supera 100?» o «¿todas las notas aprueban?».

9. Tuplas: datos inmutables

Una tupla es como una lista pero no se puede modificar:

punto = (3, 4)
print(punto[0], punto[1])   # acceso igual que en listas

x, y = punto                 # desempaquetado
print(x, y)

Salida esperada:

3 4
3 4

La tupla es perfecta para datos que van "juntos" y no cambian: coordenadas, fechas, un par (clave, valor). Al ser inmutable, puede usarse como clave de un dict (las listas no pueden).

Desempaquetado

Asignar varios valores a la vez es desempaquetar:

a, b, c = 1, 2, 3
print(a, b, c)

Salida esperada:

1 2 3

Es como "trocear" la secuencia en variables. Se usa muchísimo (lo verás al recorrer dicts o con zip).

10. enumerate, zip, map, filter

Estas funciones te permiten recorrer y transformar sin escribir bucles largos.

10.1 enumerate: índice + valor

for i, valor in enumerate(["x", "y", "z"], start=1):
    print(i, valor)

Salida esperada:

1 x
2 y
3 z

enumerate te da la posición y el valor a la vez. Es mucho más limpio que manejarte un contador a mano.

10.2 zip: combinar varias secuencias

nombres = ["Ana", "Luis"]
edades = [20, 25]

for nombre, edad in zip(nombres, edades):
    print(nombre, edad)

Salida esperada:

Ana 20
Luis 25

zip junta los elementos por posición: el primero con el primero, etc. Termina en la secuencia más corta.

10.3 map y filter

print(list(map(str.upper, ["a", "b"])))
print(list(filter(lambda x: x > 2, [1, 2, 3, 4])))

Salida esperada:

['A', 'B']
[3, 4]
  • map(función, iterable) aplica la función a cada elemento.
  • filter(función, iterable) se queda con los elementos donde la función devuelve True.

str.upper es el método que pasa una cadena a mayúsculas, usado aquí como función; y lambda x: x > 2 es una función breve que devuelve True para los elementos mayores que 2 (se estudia en 1.8 · Funciones).

map/filter o comprehension

En general, una comprehension es más legible para lo mismo: [x.upper() for x in ["a", "b"]]. Usa map/filter cuando ya tengas una función definida (no un lambda) o quieras expresar un proceso encadenado. Recuerda: map/filter devuelven iteradores (entregan los valores de uno en uno, bajo demanda), por eso los envolvemos en list() para verlos.

11. Lista vs tupla: ¿cuándo cada una?

Situación Usa
Colección que cambia (añadir, ordenar, modificar) lista [...]
Datos fijos que van juntos (coord., par clave/valor) tupla (...)
Usar como clave de un dict tupla (inmutable)
Recorrer sin cambiar nada cualquiera (la tupla ocupa un poco menos de memoria)

Regla práctica: si no vas a modificar la colección, una tupla es más segura (nadie la cambia por accidente y comunica "esto no cambia"). Para el resto, lista.

Errores frecuentes

  • Confundir índice 0 con "el primero es 1".
  • Creer que lista[1:3] incluye el índice 3 (no lo incluye).
  • Pedir una posición que no existe (numeros[10] en una lista de 5): IndexError: list index out of range.
  • Añadir elementos en un bucle con +: append es más claro y eficiente.
  • Usar numeros.sort() esperando que devuelva la lista ordenada (devuelve None), por ejemplo en ordenada = numeros.sort().
  • Olvidar que las tuplas son inmutables e intentar t[0] = 5.
  • Creer que b = a copia la lista (es una referencia; usa a.copy()).
  • Usar is para comparar el contenido de dos listas (usa ==).
  • No usar key en sorted/min/max con listas de tuplas o diccionarios.
  • No hacer una copia antes de modificar una lista que reutilizas.

Práctica de transferencia

Dada la lista notas = [7.5, 4.0, 9.0, 6.5, 3.0]:

  1. Muestra la nota más alta, la más baja y la media (sum/len).
  2. Con una comprehension, crea aprobadas = [nota for nota in notas if nota >= 5].
  3. Usa enumerate para imprimir cada nota con su posición (1: 7.5…).
  4. Convierte a tupla las notas y comprueba que no puedes cambiarla (¿qué error da?).
  5. Usa zip para juntar ["Ana", "Luis", "Marta"] con notas y muestra cada par. ¿Cuántos pares salen? ¿Por qué no salen cinco?
  6. Demuestra la diferencia entre b = notas (referencia) y c = notas.copy() (copia): cambia un elemento de la copia y explica qué pasa con el original.
  7. Con any()/all(), comprueba si hay alguna nota mayor que 9 y si todas superan el 4.

Producto evaluable

Primera versión del cuaderno 05_colecciones.py (se ampliará en 1.7) con:

  1. Los ejercicios 1–5 anteriores, cada uno con su celda de código y su interpretación en Markdown.
  2. Una celda que compare lista vs tupla (cambiar un elemento de cada una) y explique la diferencia.
  3. Una celda que muestre la diferencia entre referencia (b = a) y copia (a.copy()), con su interpretación.
  4. Una celda final que use una comprehension (o any/all) para filtrar y transformar, justificando la elección.

Formato de entrega: cuaderno marimo ejecutado, con salida esperada, interpretación y conclusión.

Resumen y referencia rápida

Tarea Sintaxis
Crear lista / tupla [1,2,3] / (1,2,3)
Acceder l[0], l[-1]
Sub-lista l[inicio:fin:paso]
Añadir/quitar append, pop, remove, del
Ordenar lista.sort() (la propia lista) · sorted(lista) (nueva)
Ordenar por campo sorted(xs, key=lambda x: x[1])
Copiar a.copy() / a[:] / list(a) (no b = a)
Comprehension [x**2 for x in lista if x > 2]
Índice+valor enumerate(seq, start=1)
Unir secuencias zip(a, b)
Transformar / filtrar map(f, seq) · filter(f, seq)
Comprobar any(...), all(...)
Indexar anidado m[fila][columna]

Siguiente: 1.7 · Diccionarios y conjuntos, que añadirá diccionarios y conjuntos al mismo cuaderno.