カテゴリー

Python基礎

【辞書の結合】dictのマージ

2021年4月9日

こんな人にオススメ


list+記号で配列の足し算(結合)ができるけど、dictってどうすれば結合できるの?
listdictで名前が似てるから同じく+を使うの?それとも他の方法が必要?

ということで、今回は執筆者のよく使っているdictのマージ(結合)について解説する。というのも、執筆者、大学院を修了するにあたってPCを新しく乗り換えた。それまでは2017年モデルのMacbook Proを使用していたのをM1 Macbook Proにし、python環境を再構築した。
その際にpythonのバージョンがそれまでの3.8系ではなく3.9系を選んだ。これによりdictのマージに新たな機能として|でのマージが使えるようになったのだ。

執筆者の人生概略については以下参照。


執筆者がM1Macbookに入れているアプリについては以下参照。
【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 もっと便利に ...

続きを見る

 

本記事ではこの|を実際に使ってみたいという思いを発端に、色々なdictのマージについて解説する。

python環境は以下。

  • Python 3.9.2
目次(クリックでジャンプ)
  1. dictの基礎
  2. dictのマージ
  3. マージ方法には注意

運営者のメガネです。YouTubeTwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

dictの基礎

ここではまずdictとは、ということとざっくりとした使い方を解説する。

dictとは

そもそもdictとは何かということで、dictとはざっというと

keysvaluesを紐づけることで値を管理する配列

ということ。実際にコードで見ると以下のようなもの。

# dictの作成
dct = {'a': 10, 'b': [1, 2, 3], 'c': (10, 20, 30), 'd': 'd daze'}
dct = dict(a=10, b=[1, 2, 3], c=(10, 20, 30), d='d daze')
print(dct)
# {'a': 10, 'b': [1, 2, 3], 'c': (10, 20, 30), 'd': 'd daze'}

2つの作成方法を書いたが、作り方についてはどちらの方法でも大丈夫。また、空のdictに要素を追加することも可能。

# 要素の追加
dct['e'] = 'add!'
print(dct)
# {'a': 10, 'b': [1, 2, 3], 'c': (10, 20, 30), 'd': 'd daze', 'e': 'add!'}

要素の取り出し方は、'a'とか'b'.keys()で、10とか[1, 2, 3]とかは.values()で可能。ただし、取り出した後の配列のtypeが特殊なのでlistに変換するのが楽。また、keysvaluesも同時に取り出したい時は.items()を使う。

# 要素の取り出し
keys = dct.keys()
values = dct.values()
print(keys)
# dict_keys(['a', 'b', 'c', 'd', 'e'])
print(type(keys))
# <class 'dict_keys'>
print(list(keys))
# ['a', 'b', 'c', 'd', 'e']

print(values)
# dict_values([10, [1, 2, 3], (10, 20, 30), 'd daze', 'add!'])
print(type(values))
# <class 'dict_values'>
print(list(values))
# [10, [1, 2, 3], (10, 20, 30), 'd daze', 'add!']

items = dct.items()
print(items)
# dict_items([('a', 10), ('b', [1, 2, 3]), ('c', (10, 20, 30)), ('d', 'd daze'), ('e', 'add!')])
print(type(items))
# <class 'dict_items'>
print(list(items))
# [('a', 10), ('b', [1, 2, 3]), ('c', (10, 20, 30)), ('d', 'd daze'), ('e', 'add!')]

forループでdctのまま回すとkeyが取り出される。keysvaluesも取り出したい時は.items()で両方回すのが楽。

# ループ処理
for k in dct:
    print(k)
# a
# b
# c
# d
# e

for k, v in dct.items():
    print(f"keys: {k}, values: {v}")
# keys: a, values: 10
# keys: b, values: [1, 2, 3]
# keys: c, values: (10, 20, 30)
# keys: d, values: d daze
# keys: e, values: add!

dictのマージとは

本記事のメインテーマであるdictのマージとは簡単にいうと2つ以上のdictを結合するということ。listの場合だと単純に+で足せばよかった。しかしdictの場合はできない。ではどうすればいいのかというのが本記事の内容。

# listでのマージ
lst1 = [1, 2, 3]
lst2 = ['1', '2', '3']
lst1_2 = lst1 + lst2
print(lst1_2)
# [1, 2, 3, '1', '2', '3']

# dictでのマージは+ではできない
dct1 = {'a1': 1, 'b1': 10, 'c1': 100}
dct2 = {'a2': 2, 'b2': 20, 'c2': 200}
dct1_2 = dct1 + dct2
# TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

dictのマージ

今回dictのマージとして使用するdictは以下の5つ。dct1_samedct1keyの一つ'a1'を持つdictで、上書きされたかどうかを確認する用。

# 今回使用する辞書の登場
dct1 = {'a1': 1, 'b1': 10, 'c1': 100}
dct2 = {'a2': 2, 'b2': 20, 'c2': 200}
dct3 = {'a3': 3, 'b3': 30, 'c3': 300}
dct4 = {'a4': 4, 'b4': 40, 'c4': 300}

# keysが被った時の動作を調べる用のdct1と同じkeysを持つ辞書
dct1_same = {'a1': 'Overwrite!'}

forループ

まずはシンプルにforループで回してマージ。最終的なdictdct1_2を空で作成し、その中に要素を入れていく感じ

# ループで回す
dct1_2 = {}
for key1 in dct1:
    dct1_2[key1] = dct1[key1]
for key2 in dct2:
    dct1_2[key2] = dct2[key2]
print(dct1_2)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

同じkeys を要素として追加すると上書きされる。

# 同じkeysは上書きされる
for key1_same in dct1_same:
    dct1_2[key1_same] = dct1_same[key1_same]
print(dct1_2)
# {'a1': 'Overwrite!', 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

.updateで破壊的にマージ

.updateを使うことでもマージできるが、この場合は使ったdictが更新される、すなわち破壊的である。元のdictを更新したい場合はいいが、元のdictはそのままにしておきたい場合はこの方法はダミーを作成する必要がある。

# dct1が破壊されるので、犠牲になってくれるダミーを作成
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update(dct2)
print(dct1_dummy)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}
print(dct1)
# {'a1': 1, 'b1': 10, 'c1': 100}

今回もforループと同様、同じkeysは上書きされる。dict1_dummyは上書きされているので毎回ダミーを用意する。

# 同じkeysは上書きされる
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update(dct2)
dct1_dummy.update(dct1_same)
print(dct1_dummy)
# {'a1': 'Overwrite!', 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

updateの中身を複数dictにした場合はエラー。この場合はupdateの中身でdictを展開すれば問題ない。もちろん同じkeysは上書きされる。

# 2つ同時はエラー
# dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
# dct1_dummy.update(dct2, dct3)
# TypeError: update expected at most 1 argument, got 2

# 展開すればOK
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update(**dct2, **dct3)
print(dct1_dummy)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

# 同じkeysは上書きされる
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update(**dct2, **dct3, **dct1_same)
print(dct1_dummy)
# {'a1': 'Overwrite!', 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

update内で展開している時に最後に展開していないdictを入れるのはエラー。この場合は展開前にdictを入れるか、入れたいdict自身も展開するか。前者の場合は最後尾にはならないので注意。

# 最初に辞書を置くのはOK(順番が意図してものにならない)
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update({'ex': 1000}, **dct2, **dct3)
print(dct1_dummy)
# {'a1': 1, 'b1': 10, 'c1': 100, 'ex': 1000, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

# 展開してあげればOK
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy.update(**dct2, **dct3, **{'ex': 1000})
print(dct1_dummy)
# 'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300, 'ex': 1000}

dictで直接展開

dictの中で直接展開するのも一つの方法。この場合はdict()でも{}でもどちらでも動作する。 同じkeysは上書きされる。3つ以上のdictでも対応可能。

# 辞書内で展開してあげればOKかつ非破壊的
dct1_2 = dict(**dct1, **dct2)
print(dct1_2)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

dct1_2 = {**dct1, **dct2}
print(dct1_2)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

# 同じkeysは上書きされる
dct1_2_1same = {**dct1, **dct2, **dct1_same}
print(dct1_2)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

# 3つ以上もOK
dct1_2_3 = dict(**dct1, **dct2, **dct3)
print(dct1_2_3)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

dct1_2_3_4 = {**dct1, **dct2, **dct3, **dct4}
print(dct1_2_3_4)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300, 'a4': 4, 'b4': 40, 'c4': 300}

|を使ったマージ

さて、本記事を書こうと思ったきっかけになった|を使ったdictのマージ。こちらも前節の「dictで直接展開」と同様に並べて書くことができる。

# python3.9から|でマージ可能に
dct1_2 = dct1 | dct2
print(dct1_2)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

# 3つ以上もOK
dct1_2_3 = dct1 | dct2 | dct3
print(dct1_2_3)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

# 同じkeysは上書きされる
dct1_2_1same = dct1 | dct2 | dct1_same
print(dct1_2_1same)
# {'a1': 'Overwrite!', 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

さらに|の場合は代入演算子のように扱うこともできる。この場合もkeysは上書きされる。

# 代入演算子っぽく扱うことも可能
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy |= dct2
print(dct1_dummy)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

# 同じkeysは上書きされる
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy |= dct1_2_1same
print(dct1_dummy)
# {'a1': 'Overwrite!', 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200}

右辺に2つ以上のdictを入れた場合はエラー。この場合はdictで展開すればOK。

# 右辺を2つ以上入れるとエラー
# dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
# dct1_dummy |= dct2, dct3
# ValueError: dictionary update sequence element #0 has length 3; 2 is required

# 展開して入れればOK
dct1_dummy = {'a1': 1, 'b1': 10, 'c1': 100}
dct1_dummy |= {**dct2, **dct3}
print(dct1_dummy)
# {'a1': 1, 'b1': 10, 'c1': 100, 'a2': 2, 'b2': 20, 'c2': 200, 'a3': 3, 'b3': 30, 'c3': 300}

ChainMapを使った方法

最後にマニアックな方法としてChainMapを使用した方法について紹介する。ChainMapは標準ライブラリのcollectioinsの中にある。単純にChainMapを適用しただけだとネストされた辞書になるだけ。しかし、dictの中に入れてあげればマージすることができる。ここで注意しないといけないのが、dictに変換したときには順番がネストのdictの並びの逆になるということ。今回ではdict1, 2, 3の順番で作成したが、最終的な順番はdict3, 2, 1の配列になっている。

from collections import ChainMap

# ChainMapだけだとネストされたdict
chainmap1_2_3 = ChainMap(dct1, dct2, dct3)
print(chainmap1_2_3)
# ChainMap({'a1': 1, 'b1': 10, 'c1': 100}, {'a2': 2, 'b2': 20, 'c2': 200}, {'a3': 3, 'b3': 30, 'c3': 300})

# typeは<class 'collections.ChainMap'>
print(type(chainmap1_2_3))
# <class 'collections.ChainMap'>

# dictでくくるとdictに変換できるが、並びが逆になる
print(dict(chainmap1_2_3))
# {'a3': 3, 'b3': 30, 'c3': 300, 'a2': 2, 'b2': 20, 'c2': 200, 'a1': 1, 'b1': 10, 'c1': 100}

並び方が逆になるので、上書きされるときも右側のkeyではなく左側のkeyが上書きされる。

# 同じkeysは左のkeys優先で上書きされる
chainmap1_2_1same = ChainMap(dct1, dct2, dct1_same)
print(chainmap1_2_1same)
# ChainMap({'a1': 1, 'b1': 10, 'c1': 100}, {'a2': 2, 'b2': 20, 'c2': 200}, {'a1': 'Overwrite!'})
print(dict(chainmap1_2_1same))
# {'a1': 1, 'a2': 2, 'b2': 20, 'c2': 200, 'b1': 10, 'c1': 100}

# この場合はdct1_sameのvaluesで上書きされる
chainmap1_2_1same = ChainMap(dct1_same, dct1, dct2)
print(chainmap1_2_1same)
# ChainMap({'a1': 'Overwrite!'}, {'a1': 1, 'b1': 10, 'c1': 100}, {'a2': 2, 'b2': 20, 'c2': 200})
print(dict(chainmap1_2_1same))
# {'a2': 2, 'b2': 20, 'c2': 200, 'a1': 'Overwrite!', 'b1': 10, 'c1': 100}

なお、ChainMapdictにする際にdict()ではなく{}を使うとsetと認識されてエラーになるので注意。

# {}だとsetに考えられると思うので、ハッシュ可能ではないということでエラー
# print({chainmap1_2_1same})

最後に注意点。ChainMapの状態で要素の変更をしてしまうと、元のdictの変更した要素も変更されるので注意。

# ChainMapで要素を変更すると、元のdictの中身も変更される
chainmap1_2_3['a1'] = 'Change!'
print(chainmap1_2_3)
# ChainMap({'a1': 'Change!', 'b1': 10, 'c1': 100}, {'a2': 2, 'b2': 20, 'c2': 200}, {'a3': 3, 'b3': 30, 'c3': 300})
print(dct1)
# {'a1': 'Change!', 'b1': 10, 'c1': 100}

一度dictに変更してしまえば変更されない。

# dictの状態で変更したら大丈夫
# 上書きされたので、再度dct1定義
dct1 = {'a1': 1, 'b1': 10, 'c1': 100}
chainmap1_2_3 = ChainMap(dct1, dct2, dct3)
# dictに変換する
chainmap1_2_3todct = dict(chainmap1_2_3)
print(chainmap1_2_3todct)
# {'a3': 3, 'b3': 30, 'c3': 300, 'a2': 2, 'b2': 20, 'c2': 200, 'a1': 1, 'b1': 10, 'c1': 100}
print(dct1)
# {'a1': 1, 'b1': 10, 'c1': 100}

マージ方法には注意

最近、自分で書いているコードにdictが多発している。基本的にはスペクトルデータを入れているが、そのスペクトルの名称をkeysにしてスペクトルをvaluesにするという感じだ。作成したスペクトルの入ったdictをマージする際にはこれからは|を使ってみようかなと思う。

ただし、python3.9から導入された方法なので他の環境で使う可能性があるのであれば注意が必要だ。

スマホ

2023/1/21

【Galaxy S22 Ultraレビュー】これが最高峰

こんな人におすすめ 2022年最強のスマホGalaxy S22 Ultraって実際使った感じどうなの ...

完全ワイヤレスイヤホン(TWS)

2023/1/15

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2023/1/14

【SONY WF-1000XM4 vs SENNHEISER MTW3】MTW3を選んだ決定的な3つの理由

こんな人におすすめ 執筆者は「SONY WF-1000XM4」「SENNHEISER MOMENTUM True Wireless 3」両方を持っている ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/1/5

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

マウス

2023/1/14

【Logicool MX ERGOカスタム】Logi Optionsのジェスチャーボタン設定内容

こんな人におすすめ Logicool MX ERGOをもっと上手に効率的に使いこなしたい。 ボ| ...

生活に役立つ

2023/1/8

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

完全ワイヤレスイヤホン(TWS)

2023/1/15

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

スイッチボット

2023/1/14

【SwitchBot Hub Mini】アプリにないエアコンなどの家電をその他で登録する方法

こんな人におすすめSwitchBot Hub Miniに我が家のエアコンを登録したいけど、SwitchBotア ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。

自己紹介と半生→変わって楽しいの繰り返し レビュー依頼などお問い合わせ Twitter@m_ten_pa

-Python基礎
-