Saltar al contenido

# conceptos-avanzados-en-python

Conceptos Avanzados en Python

Volver a la portada del curso
Lectura (Markdown)

Tuplas 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 escribe set().
  • 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.

Recursos