私が歌川です

@utgwkk が書いている

Python で2つの辞書をマージした新しい辞書を作りたいとき

要件

  • 辞書 d1d2 をマージして新しい辞書を作りたい.
  • 重複するキーがあった場合は d2 の値を優先することにする.
    • たとえば {'a': 1, 'b': 2}{'b': 3, 'c': 4} をマージすると {'a': 1, 'b': 3, 'c': 4} となってほしい.
  • マージをする過程で d1d2 の中身は書き換えたくない.

方法

dict(d1, **d2)

d2 をキーワード引数として展開して dict のコンストラクタに渡すという方法.

キーワード引数が与えられた場合、キーワード引数とその値が位置引数から作られた辞書に追加されます。既に存在しているキーが追加された場合、キーワード引数の値は位置引数の値を置き換えます。

4. 組み込み型 — Python 3.6.1 ドキュメント

確かにそれらしいが謎ハックぽさが否めないので,いきなり披露されるとややびっくりしそう.

dict(d1.items() + d2.items())

dict.items() が返す (k, v) のリストを結合したリストをもとに dict を作るという方法. マージしているんだろうなあというのは式から見える気がするが,いちいちリストに変換するのでコストが大きい. Python3系では dict.items() がビューオブジェクトを返すようになったため,この方法は2系のみでしか使えない. itertools.chain を使うことでイテレータに対しても同等のことができる(追記参照).

dict(itertools.chain(d1.items(), d2.items())

newdict = dict(d1); newdict.update(d2)

一時変数 newdictd1 のコピーを作って,そこに d2 をマージするという方法. やっていることは一番素直なのではないだろうか. ただし一時変数が必要となる.

{**d1, **d2} (追記参照)

辞書リテラル内で辞書展開記法を使うという方法. 文字数が一番短かくてすっきりした印象があってよさそう. ただしPython3.5以上でしか使えない.

まとめ

newdict = dict(d1)
newdict.update(d2)

が一番素直かなあという感想.

辞書リテラルに対して直接マージしたい!! というときは,

newdict = dict({'a': 1, 'b': 2}, **d2)

とやるのがまあいいかなあと思っている. Python3.5以上なら,

{**d1, **d2}

のほうがスマートな感じがする(追記参照).

追記

items 結合できないと思っていましたが itertools.chainイテレータを連続させられるので確かに同等なことができますね. 辞書リテラルの中でもキーワード引数展開記法が使えるのは知らなかった…….この記法なら辞書リテラルに対しても直接マージできそう.