2.37. Tupluri denumite și cozi cu două capete (deque)

Listele, tuplurile, dicționarele și mulțimile acoperă majoritatea nevoilor de date. Alte trei containere din modulul collections se potrivesc unor probleme specifice pe care tipurile încorporate le gestionează stângaci.

2.37.1. namedtuple – înregistrări tipizate fără o clasă

Un tuplu simplu stochează valorile după poziție. Acest lucru este în regulă pentru un tuplu mic și de scurtă durată, cu 2 sau 3 elemente, dar dincolo de asta point[0] și point[1] încep să mintă despre ce conțin. collections.namedtuple() returnează o nouă subclasă de tuplu ale cărei câmpuri au nume:

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

Argumentul câmpurilor este o secvență de șiruri de nume (sau un singur șir separat prin spații albe în CPython; MicroPython este mai strict – transmiteți un tuplu sau o listă).

De ce să folosiți un namedtuple în loc de o clasă?

  • Este un tuplu. Iterarea, despachetarea, egalitatea, calculul de hash și utilizarea ca cheie de dicționar funcționează toate gratuit.

  • Este imuabil. Reatribuirea r.temp = ... ridică AttributeError, ceea ce este exact ce doriți pentru un tip de înregistrare.

  • Costă mai puțină memorie RAM decât o instanță de clasă cu aceleași câmpuri – stocarea tuplului este contiguă, fără __dict__.

Comparativ cu clasa echivalentă, o declarație namedtuple ocupă o singură linie. Compromisul este că câmpurile sunt doar pentru citire – pentru a „modifica” o citire, creați una nouă.

2.37.2. deque – un tampon circular (ring buffer) mărginit

O listă este rapidă la capăt (append / pop) și lentă la început (insert(0, ...) / pop(0) deplasează ambele fiecare alt element). Un collections.deque este rapid la ambele capete – este un tampon circular (ring buffer) indexat prin pointeri de cap și de coadă, astfel încât adăugarea și extragerea de oricare parte rulează cu aceeași cantitate fixă de muncă, indiferent de câte elemente conține deque-ul.

Construirea în MicroPython necesită atât un iterabil inițial, cât și o lungime maximă, în această ordine:

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

Când un deque mărginit este plin, fiecare append elimină cel mai vechi element. Acest lucru face deque-ul ideal pentru „ultimele N eșantioane”, „ultimele N linii de jurnal” sau orice fereastră glisantă care nu trebuie să crească la nesfârșit.

Metodele expuse pe deque-ul din MicroPython sunt în mod deliberat minimale:

  • append(x) – adaugă în dreapta.

  • appendleft(x) – adaugă în stânga.

  • extend(iterable) – adaugă fiecare element din iterabil.

  • pop() – elimină și returnează capătul din dreapta. Ridică IndexError dacă este gol.

  • popleft() – elimină și returnează capătul din stânga.

Omisiuni notabile față de deque-ul din CPython: fără clear, count, index, remove, reverse, rotate, fără atributul maxlen sau __contains__. Iterarea și indexarea prin subscript funcționează:

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

O utilizare tipică: păstrarea ultimelor câteva citiri de senzori pentru a detecta schimbările de tendință:

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 – când ordinea face parte din egalitate

dict obișnuit a păstrat ordinea de inserare începând cu MicroPython 1.13 și CPython 3.7. Acest lucru acoperă cel mai frecvent motiv pentru care oamenii apelau înainte la collections.OrderedDict.

Ce vă oferă în continuare OrderedDict și un dicționar simplu nu vă oferă:

  • Egalitatea OrderedDict ține cont de ordine. Două dicționare obișnuite sunt egale ori de câte ori au aceleași perechi cheie/valoare, indiferent de ordinea de inserare. Două instanțe OrderedDict sunt egale doar dacă perechile lor sunt în aceeași ordine:

    >>> 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 este util atunci când serializați configurația într-un format căruia îi pasă de ordinea cheilor (TOML, unii consumatori YAML) sau când doriți un indiciu clar de documentare că ordinea contează pentru cititorii codului dumneavoastră.

Pentru codul de zi cu zi, preferați dict încorporat. Apelați la OrderedDict doar atunci când semantica ordine-ca-parte-din-egalitate sau valoarea documentară chiar aduce ceva.

Cele trei containere au fiecare o singură potrivire restrânsă. Tuplurile denumite înlocuiesc clasele de înregistrare scrise manual; deque-urile înlocuiesc listele pentru cozi mărginite; dicționarele ordonate fac din ordinea de inserare parte a contractului.