はじめに
2020年10月にリリースが予定されているPython3.9で新たに加わる変更をPython3.9の新機能 (まとめ)という記事でまとめ始めました。その中で比較的分量のある項目を別記事に切り出すことにしましたが、その第一弾として辞書型で和集合オペレータを使えるようになる変更を取り上げてみたいと思います。
辞書型の統合
辞書型はPython組み込みの標準データ型の一つです。他の言語だとハッシュ型・マップ型・連想配列型など様々な呼ばれ方をしていますが、要は名前と値のペア (key-value pair)を格納するためのデータ型です。
この辞書型のデータ(d1, d2) が二つあり、それを混ぜ合わせて一つの辞書型データにすることを考えます。これまでも幾つかやり方がありました。
1) update
メソッドを使う
d1.update(d2)`とすれば統合された辞書型データがd1に上書き格納されます。もしd1をそのまま取っておきたい場合には
e = d1.copy()
e.update(d2)
と一度コピーを作ってからupdate
メソッドを呼ぶ必要があります。微妙に面倒ですね。
2) 中身を取り出して並べちゃう
{**d1, **d2}
として、それぞれの辞書型から中身のデータを取りだし、新たな辞書型に並べて返します。中間変数なしに統合した辞書型データを得られるけど、パット見で何やっているかわかりにくいですね。Pythonのオリジナルの作者の Guidoさんですら「こういうやり方あったって忘れてたよ」って言うくらい、わかりにくいです(笑)
3) collections.ChainMap
を使う
collections
モジュールには ChainMap
というクラスがあって、dict(ChainMap(d2, d1))
とすると統合した辞書型データが得られる。ややこしいのは、上の例と違って、同じキーがあった場合、左側に来る方が優先されるということ。標準とは言え、collections
モジュールをインポートしなければならないし、この方法もあまり直感的では無いですね。
4) 辞書型のコンストラクタを使う
辞書型のコンストラクタで dict(mapping, **kwarg)
というのがあるので、これを使って dict(d1, **d2)
とすると統合した辞書型データが得られる。**d2
とすることでd2
の中身がキーワード引数として与えられて、第1引数の内容に追加されていきます(d1
は変化しません) 。このやり方の問題は、d2
の中身をキーワード引数として与えるので、キーが文字列な辞書型じゃないと統合できないことですね。
和集合演算子と辞書型
Set型で使われている演算子に|
と|=
があって、例えばこんな感じで使えます。
>>> a = set((1,2,3))
>>> b = set((3,4,5))
>>> a | b
{1,2,3,4,5}
>>> a |= b
>>> a
{1,2,3,4,5}
二つの集合の「和」を計算するので、和集合演算子ですね。この和集合演算子を辞書型データの統合に使っちゃいましょうというのが、今回 3.9に入る予定の変更です。
仕様はとても単純で、Set型と同じように二つ辞書型データの和を取ります。Setと違うのはキーが重なった時の対応で、Setは重なっても2重カウントしなければ良いだけだけれども、辞書型の場合はキーだけじゃなく値もあるのでそれのどちらを採用するかが問題になる。今回の変更では「あと勝ち」つまり式の上で右側の方の値が優先されます。左から順番に重ね合わせて同じキーのものは上書きするイメージですね。
例で見た方が早いですね。
>>> c = {'a': 1, 'b': 2}
>>> d = {'b': 3, 'c': 7}
>>> c | d
{'a': 1, 'b': 3, 'c': 7}
>>> d | c
{'a': 1, 'b': 2, 'c': 7}
>>> c |= d
>>> c
{'a': 1, 'b': 3, 'c': 7}
ここの c | d
の結果と d | c
の結果が違う(可換でない)というのは「和」の演算子としては気持ち悪いところですが、まあこういうものだと思うしか無いでしょう。
まとめ
Python 3.9で辞書型に導入される予定の和集合演算子について PEP-584の内容を元に解説してみました。これまで dict.update
を使うことが多かったと思いますが、元の辞書型を壊しちゃうので、immutableな演算をわかりやすい記法で描けるのは色々と使い所があると思います。