# conceptos-avanzados-en-python
Conceptos Avanzados en Python
Volver a la portada del cursoTuplas y Conjuntos
Sección 1 · Estructuras Avanzadas de Datos
Tuplas y conjuntos
Las listas y los diccionarios no resuelven todas las preguntas del análisis de ventas de PyShop. ¿Cuánto vendió cada combinación de ciudad y producto? ¿Qué clientes compraron teclado pero nunca el ebook? ¿Cuál fue el mejor día del mes? Las tuplas y los conjuntos (set) las responden con poco código. En esta lectura se trabaja con la lista pedidos: los 981 pedidos válidos que salen de leer el CSV de septiembre.
Inmutabilidad: por qué una tupla puede ser clave
Una tupla no se puede modificar después de creada. Por eso puede ser hashable, es decir, tener un valor de hash fijo durante toda su vida. Los diccionarios y los sets usan ese hash para ubicar cada elemento sin recorrer la colección. Por eso una tupla sirve como clave de diccionario y una lista no.
from collections import defaultdict
ventas = defaultdict(float)
for p in pedidos:
ventas[(p.ciudad, p.producto)] += p.importe
print(len(ventas)) # 25 combinaciones
print(ventas[("CDMX", "Teclado")]) # 35960.0
print(max(ventas.items(), key=lambda kv: kv[1]))
# (('Monterrey', 'Teclado'), 62930.0)
try:
ventas[["CDMX", "Teclado"]]
except TypeError as e:
print(e) # unhashable type: 'list'
Cinco ciudades por cinco productos dan 25 claves compuestas. La inmutabilidad es superficial: una tupla que contiene una lista tampoco es hashable, porque la lista interna puede cambiar. hash((1, [2])) lanza el mismo TypeError.
Tuplas como registros y desempaquetado
Una tupla agrupa datos heterogéneos que van juntos, y el desempaquetado asigna cada posición a un nombre en una línea. Funciona en un for, incluso con tuplas anidadas, y en el intercambio de variables sin temporal.
for (ciudad, producto), total in sorted(ventas.items())[:2]:
print(ciudad, producto, total)
# Bogotá Ebook de Python 33787.0
# Bogotá Libreta 12727.0
primero, segundo = "CDMX", "Lima"
primero, segundo = segundo, primero
print(primero, segundo) # Lima CDMX
namedtuple: tuplas con nombres
Con tres campos, r[2] ya no dice nada. collections.namedtuple crea una subclase de tupla cuyos campos tienen nombre: se accede con r.importe, pero sigue siendo inmutable, comparable y desempaquetable. El mejor día de septiembre se calcula así:
from collections import namedtuple
ResumenDia = namedtuple("ResumenDia", "fecha pedidos importe")
dias = defaultdict(lambda: [0, 0.0])
for p in pedidos:
dias[p.fecha][0] += 1
dias[p.fecha][1] += p.importe
resumenes = [ResumenDia(f, n, round(v, 2)) for f, (n, v) in dias.items()]
mejor = max(resumenes, key=lambda r: r.importe)
print(mejor) # ResumenDia(fecha=datetime.date(2026, 9, 6), pedidos=34, importe=32936.0)
fecha, n, importe = mejor
print(n, mejor[1], mejor._asdict()["importe"]) # 34 34 32936.0
Para «modificar» un campo se usa mejor._replace(pedidos=0), que devuelve una tupla nueva y deja intacta la original. Para valores por defecto, métodos o validación, el paso siguiente es una dataclass.
Conjuntos: pertenencia rápida y sin duplicados
Un set guarda elementos hashables sin repetir y sin orden. Convertir los 981 clientes de los pedidos en un set deja 200 clientes distintos. Además, x in conjunto consulta el hash en vez de recorrer elemento por elemento como una lista.
Su fuerza está en las operaciones de conjuntos. Con los clientes que compraron teclado y los que compraron el ebook:
clientes = [p.cliente for p in pedidos]
print(len(clientes), len(set(clientes))) # 981 200
teclado = {p.cliente for p in pedidos if p.producto == "Teclado"}
ebook = {p.cliente for p in pedidos if p.producto == "Ebook de Python"}
print(len(teclado), len(ebook)) # 71 124
print(len(teclado | ebook)) # 152 unión
print(len(teclado & ebook)) # 43 intersección
print(len(teclado - ebook)) # 28 teclado pero no ebook
print(len(teclado ^ ebook)) # 109 solo uno de los dos
print(sorted(teclado - ebook)[:3]) # ['C006', 'C021', 'C024']
Las cifras cuadran: 71 + 124 − 43 = 152, y 152 − 43 = 109. La diferencia teclado - ebook es justo la lista de clientes a quienes ofrecer el ebook. Se usa sorted porque un set no garantiza orden.
frozenset y deque
Un set es mutable, así que no puede ser clave de diccionario ni elemento de otro set. frozenset es su versión inmutable y hashable: sirve, por ejemplo, para contar qué combinaciones de productos compra cada cliente sin que importe el orden. collections.deque es una cola de doble extremo; con maxlen descarta automáticamente lo más antiguo, ideal para «los últimos N».
from collections import Counter, deque
canasta = defaultdict(set)
for p in pedidos:
canasta[p.cliente].add(p.producto)
combos = Counter(frozenset(s) for s in canasta.values())
print(len(combos)) # 31 combinaciones distintas
print(frozenset({"Taza", "Mouse"}) == frozenset({"Mouse", "Taza"})) # True
ultimos = deque(maxlen=3)
for p in pedidos:
ultimos.append(p.id)
print(list(ultimos)) # [998, 999, 1000]
Trampas comunes
{}no es un set vacío. Es un diccionario vacío; el set vacío se escribeset().- La coma hace la tupla, no el paréntesis.
(1)es el entero 1; la tupla de un elemento es(1,). - Esperar orden en un set. El orden en que se imprimen sus elementos puede cambiar entre ejecuciones; si importa, usa
sorted. - Creer que toda tupla es hashable. Si contiene una lista o un set, no lo es.
- Usar listas para pertenencia repetida. Conviene convertir la lista en set una sola vez.
Cierre
Una regla para elegir: lista para una secuencia que cambia, tupla para un registro fijo o una clave compuesta, namedtuple cuando los campos merecen nombre, set para pertenencia y operaciones entre grupos, y deque para colas o ventanas de los últimos elementos. El notebook estructuras-de-datos, adjunto a esta lección, repite cada ejemplo con los pedidos y agrega ejercicios verificados con assert.