2.37. 名前付きタプルとデック

リスト、タプル、辞書、セットはほとんどのデータニーズをカバーします。collections モジュールにある他の3つのコンテナは、組み込み型では不格好にしか扱えない特定の問題に適合します。

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では空白区切りの1つの文字列も可ですが、MicroPythonはより厳格です。タプルかリストを渡してください)。

クラスではなく namedtuple を使う理由は?

  • これはタプルです。反復、アンパック、等価比較、ハッシュ化、辞書キーとしての使用がすべてそのまま使えます。

  • これはイミュータブルです。r.temp = ... への再代入は AttributeError を発生させますが、これはレコード型にまさに望むことです。

  • 同じフィールドを持つクラスインスタンスよりもRAMの消費が少なくなります。タプルのストレージは連続しており、__dict__ がありません。

等価なクラスと比べて、namedtuple の宣言は1行です。トレードオフはフィールドが読み取り専用であることで、読み取り値を「変更」するには新しいものを作ります。

2.37.2. deque -- 境界付きリングバッファ

リストは 末尾 では高速(append / pop)ですが、先頭 では低速です(insert(0, ...)pop(0) も他のすべての要素をシフトします)。collections.deque は両端で高速です。これはヘッドポインタとテールポインタでインデックス付けされたリングバッファであり、デックが保持する要素数に関係なく、どちらの端への追加と取り出しも同じ一定の作業量で実行されます。

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個のサンプル」「直近のNlog行」、あるいは無限に成長する必要のないあらゆるローリングウィンドウに最適です。

MicroPythonのデックで公開されているメソッドは意図的に最小限です:

  • append(x) -- 右側に追加します。

  • appendleft(x) -- 左側に追加します。

  • extend(iterable) -- イテラブルから各項目を追加します。

  • pop() -- 右端を削除して返します。空の場合は IndexError を発生させます。

  • popleft() -- 左端を削除して返します。

CPythonのデックから注目すべき省略点: clearcountindexremovereverserotatemaxlen 属性、__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 の等価比較は順序を考慮します。通常の辞書2つは、挿入順序に関係なく同じキー/値のペアを持つ限り等しいと比較されます。OrderedDict インスタンス2つは、ペアが同じ順序にある場合にのみ等しくなります:

    >>> 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 に手を伸ばすのは、順序が等価性の一部であるというセマンティクスやドキュメント上の価値が実際に何かをもたらす場合のみにしてください。

3つのコンテナはそれぞれ1つの狭い適合先を持ちます。名前付きタプルは手作りのレコードクラスを置き換え、デックは境界付きキューにおいてリストを置き換え、順序付き辞書は挿入順序を契約の一部にします。