どすえのブログ

ソフトウェア開発ブログ

Pythonの辞書の欠損キー操作:setdefaultとdefaultdict

Pythonで辞書に存在しないキーで操作する際は、get()setdefault()defaultdict()などのメソッドを用いると良い。

get()による欠損キーへのアクセスは以下を参照。

setdefault()defaultdict()はよく似た挙動をするが、できる限りdefaultdict()を使う方が望ましい。

以下では、foodsという辞書がvegetableなどのカテゴリーをキーとして、set()内にcarrotのような具体的な食材を保持している状況を考える。

foods = {
    "vegetable": {"carrot"},
    "drink": {"water", "coffee"}
}

まだ存在しないキーfruitsに新しい食材orangeを追加してみる。

get()による値の追加

get()メソッドはキーが存在しなかった場合に返す値を第二引数に設定できる。新しいキーfruitsが存在しなかった場合、空のセットを返しorangeをセットに追加し、fruitsに対して再代入する。

fruits = foods.get("fruits", set())
fruits.add("orange")
foods["fruits"] = fruits
print(foods)


get()による値の追加

setdefault()メソッドを使うと、先ほどの処理を1行で書くことができる。

foods.setdefault("fruits", set()).add("orange")
print(foods)


setdefaultよりdefaultdictを使おう

setdefault()によりコードはシンプルになったが、場合によっては効率的でないことがある。例えば、次のようなクラスのインスタンスが持つ辞書によって、動的に内部状態を管理することを考える。

class Foods:
    def __init__(self):
        self.data = {}
        
    def add(self, category, food):
        self.data.setdefault(category, set()).add(food)

このクラスのユーザーは以下のように値を追加することができる。

foods = Foods()
foods.add("fruits", "orange")
foods.add("bread", "bagel")
print(foods.data)
# {'fruits': {'orange'}, 'bread': {'bagel'}}

しかし問題点がひとつある。foods.add()が呼ばれるたびに、キーの欠損の有無に関わらず、必ず一度setインスタンスの作成が行われている。コストのかかる余分なインスタンスの割り付けを回避するために、defaultdictクラスを使うと良い。

組み込みモジュールcollectinosdefaultdictクラスには、キーが存在しない場合のデフォルト値生成関数を指定することができる。これにより、キーが存在しない場合に限り、デフォルト値の生成が実行される。defaultdictを使ってFoodsクラスを書き換える。

from collections import defaultdict

class Foods:
    def __init__(self):
        self.data = defaultdict(set)
        
    def add(self, category, food):
        self.data[category].add(food)

使用方法はsetdefault()のときと同じであるが、内部のメモリ効率が向上している。

foods = Foods()
foods.add("fruits", "orange")
foods.add("bread", "bagel")
print(foods.data)
# defaultdict(<class 'set'>, {'fruits': {'orange'}, 'bread': {'bagel'}})


参考