組み込みdictのget()・setDefault
Dictを扱うための3つの基本的な演算とは、キーとその値へのアクセス, 代入, 削除です。
例えば、以下のようなフルーツをcounters
とするdictを定義します。
counters = {
'appple': 3,
'banana': 2
}
新たに追加するものをcounters
に増やすには、キーがあるかどうか調べ、なければデフォルトのカウンタ値0で挿入し、カウンタの値を1つ増やします。if分でキーが存在する場合にTrueを返すin文を使います。
key = 'lemon'
if key in counters:
count = counters[key]
else:
count = 0
counters[key] = count + 1
print(counters)
>>>
{'appple': 3, 'banana': 2, 'lemon': 1}
同じことを実現する別の方法として、存在しないキーの値を得たい場合にKeyError例外を使用するというものがあります。
try:
count = counters[key]
except KeyError:
count = 0
counters[key] = count + 1
print(counters)
>>>
{'appple': 3, 'banana': 2, 'lemon': 1}
存在するキーを取得するかデフォルト値を返すというこの処理の流れはとても一般的なので、組み込み型dictにはこの作業を行うgetメソッド
があります。getの第2引数は、第1引数のキーが存在しない場合にデフォルト値です。
count = counters.get(key, 0)
counters[key] = count + 1
print(counters)
>>>
{'appple': 3, 'banana': 2, 'lemon': 1}
このように、単純な方の辞書では、getメソッドを使うのが最短で最も明確なコードになります。
次に、Dictの値がlistのようなもっと複雑な方の場合はどうなるでしょうか。
buyer = {
'appple': ['ken', 'bob'],
'banana': ['shane', 'mickel']
}
key = 'lemon'
who = 'ema'
if key in buyer:
names = buyer[key]
else:
buyer[key] = names = []
names.append(who)
print(buyer)
>>>
{'appple': ['ken', 'bob'], 'banana': ['shane', 'mickel'], 'lemon': ['ema']}
今回の場合にはキーが存在しない場合には、空リストを代入しなければいけないところが異なります。3重代入文(buyer[key] = names = [])によって、2行ではなく1行でキーを追加します。Dictにデフォルト値が挿入されたら、listがその後のappend
呼び出しで変更されるため、再度代入する必要はありません。
今回も同様にKeyError例外を使うことも可能です。
try:
names = buyer[key]
except KeyError:
buyer[key] = names = []
names.append(who)
print(buyer)
>>>
{'appple': ['ken', 'bob'], 'banana': ['shane', 'mickel'], 'lemon': ['ema']}
また、同様にしてgetメソッド
で。キーがあればlist値を取得、なければ1回のアクセスと1回の代入を行うこともできます。
names = buyer.get(key)
if names is None:
buyer[key] = names = []
names.append(who)
getを使ってlist値を取得するこの方式は、if分で代入式を使用すれば、1行短縮することができ、読みやすさが向上します。
if(names := buyer.get(key)) is None:
buyer[key] = names = []
names.append(who)
print(buyer)
>>>
{'appple': ['ken', 'bob'], 'banana': ['shane', 'mickel'], 'lemon': ['ema']}
dict型には、このパターンの処理をさらに簡潔に書けるsetdefaultメソッド
もあります。
- setdefault:
辞書のキーの値を取得しようとします。キーがないと、このメソッドはそのキーに対して指定されたデフォルト値を割り当て、そのメソッドはそのキーに対する値を返します。
names = buyer.setdefault(key, [])
names.append(who)
print(buyer)
>>>
{'appple': ['ken', 'bob'], 'banana': ['shane', 'mickel'], 'lemon': ['ema']}
欠損した辞書キーを扱うのにsetdefault
を使うことが最短となるのは、デフォルト値の作成が安価で変更可能かつ例外の起こる可能性がないわスかな状況に限られます。そのような非常に特殊な場合には、getを使うためにより多くの文字と行を費やすよりもメソッド名のsetdefault
を使用する価値がありそうです。しかし、そのような状況ではdefaultdict
を使用するようにすることです。
defaultDict
組み込みcollectionsモジュールのdefaultdict
クラスには、キーが存在しない場合のデフォルト値を自動格納してこのユースケースを単純化する機能があります。
例えば、国の情報を格納したdictがあります。
countries = {
'Japan': {'Tokyo', 'Fukuoka'},
'America': {'New York'}
}
setdefault
を用いたクラスでのdictの格納の場合は以下になります。
class Countries:
def __init__(self):
self.data = {}
def add(self, country, city):
city_set = self.data.setdefault(country, set())
city_set.add(city)
countries = Countries()
countries.add('Russia', 'Yekaterinburg')
countries.add('Itary', 'Roma')
print(countries.data)
>>>
{'Russia': {'Yekaterinburg'}, 'Itary': {'Roma'}}
次にdefaultdict
を使用して書き換えてみます。
from collections import defaultdict
class Countries:
def __init__(self):
self.data = defaultdict(set)
def add(self, country, city):
self.data[country].add(city)
countries = Countries()
countries.add('France', 'Pari')
countries.add('Japan', 'Osaka')
print(countries.data)
>>>
defaultdict(<class 'set'>, {'France': {'Pari'}, 'Japan': {'Osaka'}})
addの実装が短く単純になりました。
__missing__
ファイルシステムにある写真を管理する処理を作成する時、画像の読み書きができるようにイメージのパス名にマップしてオープンするためのdictが必要です。
pictures = {}
path = 'test.png'
if (handle := pictures.get(path)) is None:
try:
handle = open(path, 'a+b')
except OSError:
print(f'Failed to open path {path}')
raise
else :
pictures[path] = handle
handle.seek(0)
image_data = handle.read()
dict型のサブクラスで特殊メソッド__missing__
を実装して欠損キーを扱うロジックを実装できます。
path = 'test.png'
def open_picture(profile_path):
try:
return open(profile_path, 'a+b')
except OSError:
print(f'Failed to open path {profile_path}')
raise
class Picture(dict):
def __missing__(self, key):
value = open_picture(key)
self[key] = value
return value
pictures = Picture()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
pictures[path]dictアクセスで、キーのpathがdictにないことがわかると、__missing__
メソッドが呼ばれる。これは、キーのデフォルト値を作成し、dictに挿入し、それを呼び出し元に返す。同じpathでアクセスした時は、すでに存在しているので、__missing__
は呼び出されません。