2.37. Іменовані кортежі та деки

Списки, кортежі, словники та множини задовольняють більшість потреб у зберіганні даних. Три інші контейнери у модулі collections підходять для конкретних задач, з якими вбудовані типи справляються незграбно.

2.37.1. namedtuple — типізовані записи без класу

Звичайний кортеж зберігає значення за позицією. Це нормально для невеликого, короткоживучого кортежу з 2 або 3 елементів, але після цього point[0] і point[1] починають приховувати, що вони зберігають. collections.namedtuple() повертає новий підклас кортежу, поля якого мають імена:

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

Аргумент fields — це послідовність рядків імен (або один рядок, розділений пробілами, у CPython; MicroPython суворіший — передавайте кортеж або список).

Навіщо використовувати namedtuple замість класу?

  • Це кортеж. Ітерація, розпакування, рівність, хешування та використання як ключа словника працюють безкоштовно.

  • Він незмінний. Присвоєння r.temp = ... викликає AttributeError, що саме те, що потрібно для типу запису.

  • Він займає менше оперативної пам’яті, ніж екземпляр класу з тими самими полями — сховище кортежу суміжне, без __dict__.

Порівняно з еквівалентним класом, оголошення namedtuple займає один рядок. Компроміс полягає в тому, що поля доступні лише для читання — щоб «змінити» показання, потрібно створити нове.

2.37.2. deque — обмежений кільцевий буфер

Список швидкий у кінці (append / pop) і повільний на початку (insert(0, ...) / pop(0) зміщують кожен інший елемент). collections.deque швидкий з обох кінців — це кільцевий буфер, індексований вказівниками голови та хвоста, тому append і pop з будь-якого боку виконуються за однаковий фіксований час незалежно від кількості елементів у деку.

Для конструювання в MicroPython потрібні і початковий ітерований об’єкт, і максимальна довжина, саме в такому порядку:

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

Коли обмежений дек заповнений, кожен append відкидає найстаріший елемент. Це робить дек ідеальним для «останніх N зразків», «останніх N рядків журналу» або будь-якого ковзного вікна, яке не потребує необмеженого зростання.

Методи, доступні у деку MicroPython, навмисно мінімальні:

  • append(x) – додати праворуч.

  • appendleft(x) – додати ліворуч.

  • extend(iterable) – додати кожен елемент з ітерованого об’єкта.

  • pop() – видалити та повернути правий кінець. Викликає IndexError при порожньому деку.

  • popleft() – видалити та повернути лівий кінець.

Помітні відсутності порівняно з деком CPython: немає clear, count, index, remove, reverse, rotate, атрибута maxlen та __contains__. Ітерація та індексація за індексом працюють:

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

Типове використання: зберігання кількох останніх показань датчика для виявлення змін тенденцій:

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 — коли порядок є частиною рівності

Звичайний dict зберігає порядок вставки починаючи з MicroPython 1.13 і CPython 3.7. Це покриває найпоширенішу причину, чому раніше зверталися до collections.OrderedDict.

Що OrderedDict дає на відміну від звичайного словника:

  • Рівність OrderedDict враховує порядок. Два звичайних словники вважаються рівними, якщо мають однакові пари ключ/значення, незалежно від порядку вставки. Два екземпляри OrderedDict рівні лише тоді, коли їхні пари розташовані в однаковому порядку:

    >>> 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 корисний, коли ви серіалізуєте конфігурацію у формат, який дійсно дбає про порядок ключів (TOML, деякі споживачі YAML), або коли ви хочете чітко задокументувати, що порядок важливий для читачів вашого коду.

Для повсякденного коду надавайте перевагу вбудованому dict. Звертайтесь до OrderedDict лише тоді, коли семантика «порядок є частиною рівності» або документаційна цінність справді дають щось корисне.

Кожен із трьох контейнерів має одне вузьке призначення. Іменовані кортежі замінюють рукописні класи записів; деки замінюють списки для обмежених черг; упорядковані словники роблять порядок вставки частиною контракту.