Semana 3: Listas, Diccionarios, Tuplas y Conjuntos
🎯 Objetivos de aprendizaje
Al final de esta semana podrás:
- Almacenar colecciones ordenadas y mutables en una
list, e indexarlas, seccionarlas y modificarlas. - Almacenar asociaciones clave-valor en un
dict, el análogo en Python de la tabla de valores de una función. - Explicar cuándo una
tuple(inmutable) o unset(sin orden, sin duplicados) encaja mejor que unalist. - Escribir comprensiones de listas y de diccionarios para construir nuevas colecciones en una sola línea.
- Trabajar con strings como secuencias, y anidar colecciones unas dentro de otras.
Lección
Listas: secuencias ordenadas
Una list es una secuencia ordenada y mutable — piénsala como una secuencia finita :
scores = [88, 92, 74, 95]
scores[0] # 88 — la indexación empieza en 0
scores[-1] # 95 — los índices negativos cuentan desde el final
scores[1:3] # [92, 74] — seccionado: [inicio, fin)
scores.append(100) # añade al final
len(scores) # 5
El seccionado scores[1:3] sigue la misma convención de intervalo semiabierto que range — es la misma idea de "inicio incluido, fin excluido" que ya usaste la semana pasada. El seccionado también acepta un paso, scores[start:stop:step], y omitir un límite significa "desde el principio" o "hasta el final":
scores[:2] # [88, 92] — todo antes del índice 2
scores[2:] # [74, 95, 100] — todo desde el índice 2 en adelante
scores[::2] # [88, 74, 100] — cada dos elementos
scores[::-1] # [100, 95, 74, 92, 88] — la lista entera, invertida
Las listas son mutables — puedes modificarlas en su lugar, no solo construir nuevas:
scores[0] = 90 # reemplazar un elemento
scores.insert(1, 100) # inserta 100 en el índice 1, desplazando el resto a la derecha
scores.remove(74) # elimina el primer 74 encontrado (por valor, no por índice)
last = scores.pop() # elimina y devuelve el último elemento
scores.sort() # ordena en su lugar, ascendente
scores.sort(reverse=True) # descendente
.sort() modifica la lista misma y devuelve None; la función incorporada sorted(scores) en cambio devuelve una lista nueva, ordenada, y deja la original sin tocar — recurre a sorted() cuando necesites conservar también el orden original.
Las comprensiones de listas son la forma pitónica de la notación de construcción de conjuntos. Compara con:
squares = [x**2 for x in range(1, 6)] # [1, 4, 9, 16, 25]
Añadir una condición refleja :
evens = [x for x in range(20) if x % 2 == 0]
Una comprensión puede transformar y filtrar a la vez — la expresión antes de for no tiene por qué ser la variable del bucle sin cambios:
passing_doubled = [s * 2 for s in scores if s >= 60]
Los strings como secuencias
Un str también se comporta como una secuencia — la indexación, el seccionado y len() funcionan igual que con una list, porque un string en realidad es una secuencia fija de caracteres:
name = "Amina"
name[0] # "A"
name[-1] # "a"
name[1:3] # "mi"
len(name) # 5
La única diferencia: los strings son inmutables — name[0] = "B" lanza un TypeError. Para "cambiar" un string, construyes uno nuevo, a menudo con un método: name.upper(), name.lower(), name.strip() (elimina los espacios en blanco alrededor), name.replace("A", "B"), o name.split(",") (divide en una list de partes — la herramienta que la Semana 5 del track Normal de Python 101 usará para procesar manualmente las filas de un archivo CSV). Ninguna de estas modifica name en sí; cada una devuelve un string (o lista) nuevo.
Diccionarios: asociaciones clave-valor
Un dict asocia claves con valores, como una función definida solo sobre un dominio finito:
ages = {"amina": 21, "youssef": 23}
ages["amina"] # 21
ages["sara"] = 19 # añade una clave nueva
"sara" in ages # True — prueba de pertenencia
for name, age in ages.items():
print(name, "is", age)
Buscar una clave inexistente con ages["missing"] lanza un KeyError — usa .get("missing", default) cuando una clave pueda no existir. Algunas otras operaciones de diccionarios que usarás constantemente:
ages.keys() # una vista de todas las claves: dict_keys(['amina', 'youssef', 'sara'])
ages.values() # una vista de todos los valores: dict_values([21, 23, 19])
del ages["sara"] # elimina una clave por completo
ages.update({"karim": 25, "amina": 22}) # añade/sobrescribe varias claves a la vez
Las comprensiones de diccionarios reflejan las comprensiones de listas, pero construyen un dict en lugar de una list:
name_lengths = {name: len(name) for name in ages}
# {'amina': 5, 'youssef': 7, 'karim': 5}
Tuplas: secuencias inmutables de forma fija
Una tuple se parece a una lista pero no puede modificarse después de creada — útil para valores que naturalmente forman un grupo fijo, como un par de coordenadas:
point = (3, 4)
x, y = point # desempaquetado (unpacking)
Como las tuplas son inmutables, pueden usarse como claves de diccionario; las listas no. Esto hace que un dict con claves de tipo tuple sea una forma natural de representar una asociación desde pares de cosas, como una coordenada de una cuadrícula hacia un valor:
grid = {(0, 0): "start", (2, 3): "treasure"}
grid[(0, 0)] # "start"
El desempaquetado de tuplas también aparece constantemente al recorrer .items() de un diccionario, como ya viste arriba: for name, age in ages.items(): desempaqueta cada tupla (name, age) en dos variables en una sola línea.
Conjuntos: colecciones únicas y sin orden
Un set es el equivalente directo en Python del concepto matemático de conjunto — sin orden, sin duplicados:
a = {1, 2, 3}
b = {2, 3, 4}
a | b # unión: {1, 2, 3, 4}
a & b # intersección: {2, 3}
a - b # diferencia: {1}
Un {} vacío es en realidad un dict, no un set (una peculiaridad histórica de la sintaxis) — usa set() para crear un conjunto vacío. Convertir una list a set y de vuelta es el truco estándar para eliminar duplicados manteniendo (en su mayoría) la idea de "solo los valores únicos":
names = ["amina", "youssef", "amina", "sara"]
unique_names = list(set(names)) # el orden no está garantizado que coincida con el original
Anidar colecciones
Las colecciones pueden contener otras colecciones —una lista de diccionarios, un diccionario de listas, y así sucesivamente— que es como se representan datos genuinamente estructurados, como varios estudiantes cada uno con varias calificaciones:
students = [
{"name": "Amina", "scores": [88, 92, 79]},
{"name": "Youssef", "scores": [74, 68, 81]},
]
for student in students:
average = sum(student["scores"]) / len(student["scores"])
print(student["name"], round(average, 1))
Esta forma exacta —una lista de diccionarios, un diccionario por registro— es muy parecida a lo que obtendrás al leer un archivo CSV en la Semana 5, y es esencialmente una versión en miniatura, construida a mano, de lo que representa un DataFrame de pandas en la Sección 2.
⚠️ Errores comunes
- Confundir
.sort()ysorted().scores.sort()modifica y devuelveNone—x = scores.sort()deja axcomoNone, una fuente común de confusión. Usasorted(scores)si necesitas el resultado como un valor. - Modificar una lista mientras la recorres. Eliminar elementos de una lista dentro de un bucle
for item in my_list:se salta elementos, porque los índices se desplazan bajo tus pies durante la iteración. Recorre una copia (for item in my_list[:]:) o construye una lista nueva en su lugar. - Olvidar que las claves de un
dictdeben ser inmutables.grid[[0, 0]] = "x"lanza unTypeError: unhashable type: 'list'— usa una tupla(0, 0)en su lugar. - Asumir que
set/dictconservan el orden de inserción como esperarías de matemáticas. Los diccionarios modernos de Python sí conservan el orden de inserción como detalle de implementación, pero los conjuntos no garantizan ningún orden particular — nunca confíes en el orden que obtienes de unset.
🧩 Retos
Dado grades = [55, 72, 88, 40, 91, 60], escribe una comprensión de lista en una sola línea que produzca solo las calificaciones aprobatorias (≥ 60).
Dada una lista de palabras, construye un dict que asocie cada palabra única con el número de veces que aparece (un "conteo de frecuencia de palabras").
Tienes dos listas de nombres de estudiantes, roster_a y roster_b. Encuentra los estudiantes que están en roster_a pero no en roster_b, sin escribir un bucle manual.
¿Guardarías una coordenada 2D (x, y) como una list o una tuple? Justifica tu elección usando lo que distingue a cada tipo.
Dada una lista de nombres, escribe una comprensión de diccionario que asocie cada nombre con la longitud de ese nombre.
Usando la lista de diccionarios students del ejemplo práctico, encuentra el nombre del estudiante con el promedio de calificaciones más alto, sin escribir directamente cuál es en el código.
🤔 Preguntas socráticas
list1 = [1, 2, 3]; list2 = list1; list2.append(4). ¿Cuál es el valor delist1ahora? ¿Por qué esto difiere de lo que podrías esperar según la discusión de la Semana 1 sobre "los nombres apuntan a valores" con números simples?- ¿Por qué no se puede usar una
listcomo clave de diccionario, pero sí unatuple? ¿Qué propiedad necesita realmente undicten la clave? {1, 2, 2, 3}— ¿a qué se evalúa esto, y por qué eso hace que unsetsea una herramienta natural para "eliminar duplicados de esta lista"?- Una
listdedicts (comostudentsarriba) y undictdelists (por ejemplo{"Amina": [88, 92, 79], "Youssef": [74, 68, 81]}) pueden representar información muy similar. ¿Qué pregunta podrías responder fácilmente con una forma pero con dificultad con la otra? - Los strings son inmutables pero las listas son mutables, aunque ambos soportan indexación y seccionado de la misma manera. ¿Qué diferencia práctica marca eso la primera vez que intentas "editar" un string en su lugar frente a una lista?