【collections.ChainMap】Pythonで複数の辞書を1つにする簡単な方法

本記事は、collections.ChainMapの基本的な使い方を説明しています。特徴を分かりやすく説明するためのコード例もつけているので、参考にしながら動作を確認していただければと思います。
本記事は、こちらのドキュメントを参考にしているため、pythonのバージョンによっては挙動が異なる場合があります。その場合は、ドキュメントを参照してください。
collections.ChainMapとは
collections — ChainMap コンテナデータ型 — Python 3.11.0b5 ドキュメント
ChainMap は、複数の辞書やその他のマッピングを結合して、一つのマップのように扱えるようにするものです。次のような特徴があります。
- 複数のマップをまとめて一つのマップのように扱える
- 辞書の順序を保持しているため、元の辞書の順序を保ったまま値を取得することができる
- 根底のマッピングを参照によって組み込んでいるため、根底のマッピングの一つが更新されるとその変更は
ChainMapに反映される
複数のマップをまとめて一つのマップのように扱える
collections.ChainMapは、複数のマップをまとめて一つのマップのように扱えます。辞書を渡すことで、それらを結合して新しいマップを作成することができます。この新しいマップでは、複数の辞書を検索するようにすることで、値を取得することができます。
たとえば、以下のように使用することができます。
from collections import ChainMap
# 複数の辞書を作成する
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
# ChainMapを使用して複数の辞書を結合する
chain = ChainMap(dict1, dict2)
# 値を取得する
print(chain['a']) # 1
print(chain['c']) # 3元の辞書を更新するとChainMapに反映される
ChainMap には、「元の辞書を更新すると、ChainMap にも反映される」という特徴があります。これは、ChainMap が元の辞書を参照によって組み込んでいるためです。
では、以下のコードで確認してみましょう。
from collections import ChainMap
# 複数の辞書を作成する
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
# ChainMapを使用して複数の辞書を結合する
chain = ChainMap(dict1, dict2)
# 値を取得する
print(chain['c']) # 3
# 元の辞書を更新する
dict2['c'] = 5
# 値を取得する
print(chain['c']) # 5元の辞書を更新後、参照元が変わったのでchain['c']の値が変更されていることが分かります。
元の辞書の順序を保ったまま値を取得できる
ChainMap には、「元の辞書の順序を保ったまま、値を取得できる」という特徴があります。これは、ChainMap が元の辞書を結合する際に元の辞書の順序を保持しているためです。
from collections import ChainMap
# 複数の辞書を作成する
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
# ChainMapを使用して複数の辞書を結合する
chain = ChainMap(dict1, dict2)
# 値を取得する
print(chain['a']) # 1
print(chain['c']) # 3
# maps属性を使って、元の辞書の順序を確認する
print(chain.maps) # [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}]後述するmaps 属性を使って元の辞書の順序を確認することができます。
collections.ChainMapの属性・メソッド・プロパティ
ChainMapは、辞書で使用できるメソッドのすべてをサポートしています。さらに、組み込まれた辞書を参照するmaps属性や、新しいサブコンテキストを作成するnew_child()メソッド、最初のマッピング以外にアクセスするためのparentsプロパティがあります。
maps:組み込まれた辞書を参照するnew_child():新しいサブコンテキストを作成するparents:最初のマッピング以外にアクセスする
maps
ChainMapのmaps属性は、ChainMapが管理するマッピングオブジェクトのリストです。
以下は、辞書オブジェクトをChainMapで管理する例です。
import collections
# 辞書オブジェクトを作成
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
d3 = {'c': 5, 'd': 6}
# 辞書オブジェクトをChainMapで管理
cm = collections.ChainMap(d1, d2, d3)
# maps属性を取得
maps = cm.maps
print(maps) # [{'a': 1, 'b': 2}, {'b': 3, 'c': 4}, {'c': 5, 'd': 6}]上記の例では、maps属性は、d1、d2、d3を要素とするリストとして取得されます。また、maps属性は、最初に作成されたd1が最初に探されるものから、最後に作成されたd3が最後に探されるものの順番で並んでいます。
このように、ChainMapのmaps属性を使用することで、管理されているマッピングオブジェクトのリストを取得することができます。
new_child(m=None)
new_child() メソッドは、新しいサブコンテキストを作成します。サブコンテキストでは、結合された辞書を継承しつつ、新しいマッピングを追加することができます。また、結合された辞書の値を取得、更新、削除することができます。ただし、サブコンテキストでの値の更新や削除は、元の辞書を直接更新や削除するのではなく、サブコンテキスト内でのみ有効です。つまり、サブコンテキストを作成することで、元の辞書を変更せずに、更新や削除を行うことができます。
from collections import ChainMap
# 辞書オブジェクトを作成
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
d3 = {'c': 5, 'd': 6}
# 辞書オブジェクトをChainMapで管理
cm = ChainMap(d1, d2, d3)
# サブコンテキストを作成
sub_cm = cm.new_child()
# サブコンテキストで値を更新
sub_cm['a'] = 8
# サブコンテキストで値を取得 (値が更新されている)
print(sub_cm['a']) # 8
# 元のChainMapで値を取得
print(cm['a']) # 1 (サブコンテキストでの更新は反映されていない)サブコンテキストで値を更新しても、元のChainMapの値は変更されていないことが分かります。
オプション:mについて
バージョン3.4でオプションとしてmが追加されました。
m 引数を指定して new_child() メソッドを使用すると、新しい子として m マップが ChainMap オブジェクトに追加されます。この新しい子は、現在の ChainMap オブジェクトのスコープ内で最初に検索されるようになります。
例えば、次のように使用できます。
from collections import ChainMap
# 複数のマップを用意する
defaults = {'color': 'red', 'user': 'guest'}
command_line_args = {'color': 'blue', 'user': 'admin'}
# ChainMapを作成する
chain = ChainMap(command_line_args, defaults)
# 新しい子を追加する
new_child = chain.new_child(m={'color': 'green', 'size': 'large'})
print(new_child) # ChainMap({'color': 'green', 'size': 'large'}, {'color': 'blue', 'user': 'admin'}, {'color': 'red', 'user': 'guest'})
print(new_child['color']) # green
print(new_child['size']) # large
print(new_child['user']) # admin上の例では、新しい子として{'color': 'green', 'size': 'large'}マップが追加されます。このマップは、new_childのスコープ内で最初に検索されるため、new_child['color']は'green'、new_child['size']は'large'、new_child['user']は'admin'を返します。
parents
parents プロパティは、現在の ChainMap インスタンスの最初のマッピング以外のすべてのマッピングを含む新しい ChainMap オブジェクトを返します。これは、最初のマッピングを検索から飛ばすのに便利です。
import collections
# 複数のマップを用意する
defaults = {'color': 'red', 'user': 'guest'}
command_line_args = {'color': 'blue', 'user': 'admin'}
# ChainMapを作成する
chain = collections.ChainMap(command_line_args, defaults)
# parentsプロパティを使用する
parent_chain = chain.parents
print(parent_chain) # ChainMap({'color': 'red', 'user': 'guest'})
print(parent_chain['color']) # red
print(parent_chain['user']) # guest上の例では、parent_chain 変数には、現在の chain の最初のマッピングを除いたすべてのマッピングが含まれる新しい ChainMap オブジェクトが格納されます。つまり、parent_chain 変数は、defaults マップのみを含む ChainMap オブジェクトです。そのため、parent_chain['color'] は 'red'、parent_chain['user'] は 'guest' を返します。
まとめ
Pythonで複数の辞書を1つにする方法として、collections.ChainMapを使用する方法を紹介しました。ChainMapを使用することで、複数の辞書を扱う際に、見やすく、簡単に処理することができます。また、ChainMapを使用することで、辞書を統合した上で、検索や値の追加、更新、削除などの操作を行うことができます。また、ChainMapを使用する場合は、注意する点として、辞書を統合する順番が重要であることがあります。辞書を統合する順番によって、同じキーを持つ値が異なる辞書に格納されている場合、どの辞書の値が参照されるかが変わります。そのため、辞書を統合する順番を意識して使用する必要があります。

コメント