2.37. Tuplas con nombre y deques

Las listas, tuplas, diccionarios y conjuntos cubren la mayoría de las necesidades de datos. Otros tres contenedores del módulo collections encajan con problemas específicos que las estructuras integradas manejan de forma torpe.

2.37.1. namedtuple – registros tipados sin una clase

Una tupla simple almacena valores por posición. Eso está bien para una tupla pequeña y efímera de 2 o 3 elementos, pero más allá de eso point[0] y point[1] empiezan a mentir sobre lo que contienen. collections.namedtuple() devuelve una nueva subclase de tupla cuyos campos tienen nombres:

>>> from collections import namedtuple
>>> Reading = namedtuple('Reading', ('temp', 'humidity', 'ts'))
>>> r = Reading(22.5, 41.0, 137204)
>>> r.temp
22.5
>>> r.humidity
41.0
>>> r[0]
22.5

El argumento de campos es una secuencia de cadenas de nombres (o una sola cadena separada por espacios en blanco en CPython; MicroPython es más estricto – pasa una tupla o lista).

¿Por qué usar una namedtuple en lugar de una clase?

  • Es una tupla. La iteración, el desempaquetado, la igualdad, el hashing y el uso como clave de diccionario funcionan todos gratis.

  • Es inmutable. Reasignar r.temp = ... lanza AttributeError, que es exactamente lo que quieres para un tipo de registro.

  • Cuesta menos RAM que una instancia de clase con los mismos campos – el almacenamiento de la tupla es contiguo, sin __dict__.

Comparada con la clase equivalente, una declaración de namedtuple es una sola línea. La contrapartida es que los campos son de solo lectura – para «cambiar» una lectura creas una nueva.

2.37.2. deque – un búfer circular acotado

Una lista es rápida en el final (append / pop) y lenta al principio (insert(0, ...) / pop(0) ambos desplazan todos los demás elementos). Una collections.deque es rápida en ambos extremos – es un búfer circular indexado por punteros de cabeza y cola, así que añadir y extraer en cualquier lado se ejecuta en la misma cantidad fija de trabajo, sin importar cuántos elementos contenga la deque.

La construcción en MicroPython requiere tanto un iterable inicial como una longitud máxima, en ese orden:

>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
...     events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]

Cuando una deque acotada está llena, cada append descarta el elemento más antiguo. Eso hace que la deque sea ideal para «las últimas N muestras», «las últimas N líneas de registro» o cualquier ventana deslizante que no necesite crecer indefinidamente.

Los métodos expuestos en la deque de MicroPython son deliberadamente mínimos:

  • append(x) – añade a la derecha.

  • appendleft(x) – añade a la izquierda.

  • extend(iterable) – añade cada elemento del iterable.

  • pop() – elimina y devuelve el extremo derecho. Lanza IndexError si está vacía.

  • popleft() – elimina y devuelve el extremo izquierdo.

Omisiones notables respecto a la deque de CPython: sin clear, count, index, remove, reverse, rotate, atributo maxlen ni __contains__. La iteración y la indexación por subíndice funcionan:

>>> events[0]
3
>>> for e in events:
...     print(e)

Un uso típico: mantener las últimas lecturas de sensor para detectar cambios de tendencia:

history = deque((), 10)

def push(reading):
    history.append(reading)
    if len(history) == 10 and history[-1] > 2 * history[0]:
        print('reading is climbing')

2.37.3. OrderedDict – cuando el orden es parte de la igualdad

Un dict normal ha conservado el orden de inserción desde MicroPython 1.13 y CPython 3.7. Eso cubre la razón más común por la que la gente solía recurrir a collections.OrderedDict.

Lo que OrderedDict todavía te aporta y un dict simple no:

  • La igualdad de OrderedDict tiene en cuenta el orden. Dos dicts normales se comparan como iguales siempre que tengan los mismos pares clave/valor, independientemente del orden de inserción. Dos instancias de OrderedDict son iguales solo si sus pares están en el mismo orden:

    >>> from collections import OrderedDict
    >>> OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)])
    False
    >>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
    True
    
  • OrderedDict es útil cuando estás serializando configuración a un formato que se preocupa por el orden de las claves (TOML, algunos consumidores de YAML), o cuando quieres una indicación documental clara de que el orden importa para los lectores de tu código.

Para el código cotidiano, prefiere el dict integrado. Recurre a OrderedDict solo cuando la semántica de orden-como-parte-de-la-igualdad o el valor documental aporten realmente algo.

Cada uno de los tres contenedores tiene un encaje estrecho. Las tuplas con nombre reemplazan las clases de registro hechas a mano; las deques reemplazan las listas para colas acotadas; los diccionarios ordenados hacen que el orden de inserción forme parte del contrato.