1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Python] OrderedDict と ChainMap - 特殊な辞書型

Posted at

はじめに

今回は2つの特殊な辞書型を学びます。
OrderedDictは「順序操作」に特化し、ChainMapは「複数の辞書を仮想的に結合」します。
どちらも通常の dict では表現しにくいユースケースを、シンプルに実装できるのが特徴です。

01-introduction.png

OrderedDict

02-ordereddict_qiita.png

基本

Python 3.7以降、通常の dict挿入順を保持します。
それでも OrderedDict が残っている理由は、順序を操作するための明示的なAPIを持っている点にあります。

from collections import OrderedDict

od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3

print(list(od.keys()))  # ['a', 'b', 'c']

move_to_end() - 要素を端に移動

from collections import OrderedDict

od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])

# 末尾に移動
od.move_to_end('a')
print(list(od.keys()))  # ['b', 'c', 'a']

# 先頭に移動
od.move_to_end('c', last=False)
print(list(od.keys()))  # ['c', 'b', 'a']

popitem() - 端から削除

from collections import OrderedDict

od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])

# 末尾から削除(デフォルト)
item = od.popitem()
print(item)  # ('c', 3)

# 先頭から削除
item = od.popitem(last=False)
print(item)  # ('a', 1)

順序を考慮した比較

from collections import OrderedDict

od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])

print(od1 == od2)  # False(順序も比較される)

⚠️ 注意:OrderedDict と dict の比較

OrderedDict通常の dict(他の Mapping) を比較する場合、
順序は無視され、通常の辞書と同じルールで比較されます。

from collections import OrderedDict

od = OrderedDict([('a', 1), ('b', 2)])
d = {'b': 2, 'a': 1}

print(od == d)  # True(順序は無視される)
  • OrderedDict 同士 → 順序を考慮
  • OrderedDict と dict → 順序を無視

この挙動は実務でも混乱しやすいポイントです。


LRUキャッシュの実装例

LRU(Least Recently Used)は、キャッシュが満杯になったときに最も最近使われていないデータから削除する方式です。

from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.capacity = capacity

    def get(self, key):
        if key not in self.cache:
            return None
        self.cache.move_to_end(key)
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)

cache = LRUCache(3)
cache.put('a', 1)
cache.put('b', 2)
cache.put('c', 3)

cache.get('a')
cache.put('d', 4)

print(list(cache.cache.keys()))  # ['c', 'a', 'd']

ChainMap

03-chainmap_qiita.png

基本

ChainMap は複数の辞書を コピーせず に、1つの辞書のように扱えるクラスです。

from collections import ChainMap

defaults = {'color': 'red', 'size': 'medium'}
user_settings = {'color': 'blue'}

config = ChainMap(user_settings, defaults)

print(config['color'])  # blue
print(config['size'])   # medium

検索順序(lookup)と反復順序(iteration)

検索(lookup)

  • 先頭 → 末尾
  • 最初に見つかった値が採用される

反復(iteration)

  • 末尾 → 先頭
  • 公式ドキュメントでは「last to first をスキャンして決定」と定義されている
from collections import ChainMap

first = {'a': 1, 'b': 2}
second = {'c': 3, 'd': 4}

chain = ChainMap(first, second)

print(list(chain.keys()))
# ['c', 'd', 'a', 'b']

※ 重複キーがある場合は、lookup と同様に 先頭の辞書の値が優先されます。


maps 属性

from collections import ChainMap

chain = ChainMap({'a': 1}, {'b': 2})
print(chain.maps)

chain['c'] = 3
print(chain.maps)

new_child() と parents

from collections import ChainMap

parent = {'x': 1, 'y': 2}
chain = ChainMap(parent)

child = chain.new_child()
child['x'] = 10
child['z'] = 3

print(child['x'])  # 10
print(child['y'])  # 2
print(parent)      # {'x': 1, 'y': 2}

OrderedDict / dict / ChainMap 比較

特徴 dict OrderedDict ChainMap
挿入順保持
順序操作API × ×
比較時に順序考慮 × ◎(同士のみ) ×
複数辞書結合 ○(コピー) ○(コピー) ◎(参照)
メモリ効率

※ ChainMap は単一辞書としての挿入順を管理しない


まとめ

06-summary_qiita.png

  • OrderedDict順序を操作したいとき に使う
  • 比較時の挙動(dict との比較)は要注意
  • ChainMapスコープ・設定の階層化 に非常に強い
  • lookup と iteration の順序は必ず分けて理解する

次回は defaultdict の実践的な使い方を扱う予定です。(仮)

解説動画 (自分用)

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?