2.37. Tuplas nomeadas e deques

Listas, tuplas, dicionários e conjuntos cobrem a maioria das necessidades de dados. Três outros contêineres no módulo collections se ajustam a problemas específicos que os tipos embutidos tratam de forma desajeitada.

2.37.1. namedtuple – registros tipados sem uma classe

Uma tupla simples armazena valores por posição. Isso é adequado para uma 2- ou 3-tupla pequena e de vida curta, mas além disso point[0] e point[1] começam a mentir sobre o que contêm. collections.namedtuple() retorna uma nova subclasse de tupla cujos campos têm nomes:

>>> 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

O argumento de campos é uma sequência de strings de nome (ou uma única string separada por espaços em branco no CPython; o MicroPython é mais estrito – passe uma tupla ou lista).

Por que usar uma namedtuple em vez de uma classe?

  • É uma tupla. Iteração, desempacotamento, igualdade, hashing e uso como chave de dicionário funcionam de graça.

  • É imutável. Reatribuir r.temp = ... levanta AttributeError, que é exatamente o que você quer para um tipo de registro.

  • Custa menos RAM que uma instância de classe com os mesmos campos – o armazenamento da tupla é contíguo, sem __dict__.

Comparada com a classe equivalente, uma declaração de namedtuple é uma linha. A contrapartida é que os campos são somente leitura – para “mudar” uma leitura você cria uma nova.

2.37.2. deque – um ring buffer limitado

Uma lista é rápida no fim (append / pop) e lenta no início (insert(0, ...) / pop(0) ambos deslocam todos os outros elementos). Uma collections.deque é rápida em ambas as extremidades – é um ring buffer indexado por ponteiros de cabeça e cauda, então append e pop em qualquer um dos lados executam a mesma quantidade fixa de trabalho, independentemente de quantos itens a deque contém.

A construção no MicroPython requer tanto um iterável inicial quanto um comprimento máximo, nessa ordem:

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

Quando uma deque limitada está cheia, cada append descarta o item mais antigo. Isso torna a deque ideal para “as últimas N amostras”, “as últimas N linhas de log” ou qualquer janela deslizante que não precise crescer para sempre.

Os métodos expostos na deque do MicroPython são deliberadamente mínimos:

  • append(x) – adiciona à direita.

  • appendleft(x) – adiciona à esquerda.

  • extend(iterable) – anexa cada item do iterável.

  • pop() – remove e retorna a extremidade direita. Levanta IndexError se estiver vazia.

  • popleft() – remove e retorna a extremidade esquerda.

Omissões notáveis em relação à deque do CPython: sem clear, count, index, remove, reverse, rotate, atributo maxlen ou __contains__. Iteração e indexação por subscrito funcionam:

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

Um uso típico: manter as últimas leituras de sensor para detectar mudanças de tendência:

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 – quando a ordem faz parte da igualdade

O dict regular preserva a ordem de inserção desde o MicroPython 1.13 e o CPython 3.7. Isso cobre o motivo mais comum pelo qual as pessoas costumavam recorrer a collections.OrderedDict.

O que OrderedDict ainda oferece que um dict simples não oferece:

  • A igualdade de OrderedDict considera a ordem. Dois dicts regulares comparam como iguais sempre que têm os mesmos pares chave/valor, independentemente da ordem de inserção. Duas instâncias de OrderedDict são iguais somente se seus pares estiverem na mesma ordem:

    >>> 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 é útil quando você está serializando configuração para um formato que de fato se importa com a ordem das chaves (TOML, alguns consumidores de YAML), ou quando você quer uma dica clara de documentação de que a ordem importa para quem lê seu código.

Para código do dia a dia, prefira o dict embutido. Recorra a OrderedDict apenas quando a semântica de ordem-faz-parte-da-igualdade ou o valor documental realmente trouxer algo.

Cada um dos três contêineres tem um encaixe estreito. Tuplas nomeadas substituem classes de registro feitas à mão; deques substituem listas para filas limitadas; dicts ordenados tornam a ordem de inserção parte do contrato.