カテゴリー

当サイトはアフィリエイトプログラムによる収益を得ています〈景品表示法に基づく表記です)

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個紹介する

続きを見る

 

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

python環境は以下。

  • Python 3.9.2

運営者のメガネです。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/11/11

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

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

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/11/4

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

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

ベストバイ

2023/10/29

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

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

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

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

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

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

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

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

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

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(31万円) x Galaxy Z Fold5(25万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa、 Instagram@megatenpa

-Python基礎
-