2.37. Named tuples e deques

Listas, tuplos, dicionários e conjuntos cobrem a maioria das necessidades de dados. Três outros contentores no módulo collections servem problemas específicos que os tipos integrados tratam de forma desajeitada.

2.37.1. namedtuple – registos tipados sem uma classe

Um tuplo simples armazena valores por posição. Isso é aceitável para um 2- ou 3-tuplo pequeno e de curta duração, mas além disso point[0] e point[1] começam a ser pouco expressivos sobre o que contêm. collections.namedtuple() devolve uma nova subclasse de tuplo 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 fields é uma sequência de strings de nomes (ou uma única string separada por espaços no CPython; o MicroPython é mais restritivo – passe um tuplo ou lista).

Porquê usar um namedtuple em vez de uma classe?

  • É um tuplo. Iteração, desempacotamento, igualdade, hashing e uso como chave de dicionário funcionam sem custo adicional.

  • É imutável. Reatribuir r.temp = ... levanta AttributeError, o que é exatamente o que se quer num tipo de registo.

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

Comparado com a classe equivalente, uma declaração de namedtuple é uma linha. A contrapartida é que os campos são apenas de leitura – para «alterar» uma leitura, cria-se uma nova.

2.37.2. deque – um ring buffer delimitado

Uma lista é rápida no fim (append / pop) e lenta no início (insert(0, ...) / pop(0) deslocam todos os outros elementos). Um collections.deque é rápido em ambas as extremidades – é um ring buffer indexado por ponteiros de cabeça e cauda, pelo que append e pop em qualquer extremidade executam na mesma quantidade fixa de trabalho independentemente do número de itens que a deque contém.

A construção em MicroPython requer tanto um iterável inicial como 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 delimitada está cheia, cada append descarta o item mais antigo. Isso torna a deque ideal para «as últimas N amostras», «as últimas N linhas de registo», ou qualquer janela deslizante que não precise de crescer indefinidamente.

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

  • append(x) – adicionar à direita.

  • appendleft(x) – adicionar à esquerda.

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

  • pop() – remover e devolver o elemento da extremidade direita. Levanta IndexError se estiver vazia.

  • popleft() – remover e devolver o elemento da extremidade esquerda.

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

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

Uma utilização típica: manter as últimas leituras do sensor para detetar 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 a razão mais comum pela qual as pessoas recorriam ao collections.OrderedDict.

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

  • A igualdade do OrderedDict considera a ordem. Dois dicionários regulares comparam-se 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 apenas se os 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
    
  • O OrderedDict é útil quando se está a serializar configuração para um formato que se preocupa com a ordem das chaves (TOML, alguns consumidores YAML), ou quando se quer uma sugestão de documentação clara de que a ordem é importante para os leitores do código.

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

Os três contentores têm cada um um enquadramento específico. Os named tuples substituem classes de registo feitas à mão; as deques substituem listas para filas delimitadas; os dicionários ordenados tornam a ordem de inserção parte do contrato.