Pythonで辞書(Dict)型を参照する2つの方法
Pythonで辞書型のオブジェクトの値を参照するのには2つの方法があります。
- そのまま参照する
- getメソッドを使う
前者は使わない方がよいのですが、 修正しました。コメント欄を参照
前者はあまり使わない方が良いのですが、その理由をご紹介します。
1. そのまま参照する場合
解説のために簡単なクラスを作成します。
class SampleDict1:
def __init__(self, params: dict):
self.name = params['name']
self.age = params['age']
self.sex = params['sex']
これはインスタンスを作成する際に、作成するための情報を辞書型の引数で渡す方法です。APIのリクエストボディにJSONで作成データを格納し、それをサーバ側で辞書型に変換してモデル層に渡すときなどにこのようなやり方をすることがあると思います。
ここで、このクラスのインスタンスを作成してみます。
from sample_class import SampleDict1
params = {'name':'test',
'age':38,
'sex':'male'}
sample_dict1 = SampleDict1(params)
print(sample_dict1.__dict__)
# {'name': 'test', 'age': 38, 'sex': 'male'}
無事に作成することができました。インスタンスを作成する際に渡す辞書型オブジェクトに、パラメータの不足がない場合は何の問題もありません。
では、辞書型オブジェクトにsexが含まれていない場合を見てみましょう。
from sample_class import SampleDict1
params = {'name':'test',
'age':38}
sample_dict1 = SampleDict1(params)
# KeyError: 'sex'
辞書型オブジェクトを直接参照している場合で、指定したkeyがオブジェクトに存在しないときは、KeyErrorという例外が発生してしまいます。
そのため、例えば、sexパラメータを必須としないなどの場合に、直接参照するやり方だと以下のように実装するはずです。
class SampleDict1:
def __init__(self, params: dict):
self.name = params['name']
self.age = params['age']
if 'sex' in params.keys():
self.sex = params['sex']
あるいは
class SampleDict1:
def __init__(self, params: dict):
try:
self.name = params['name']
self.age = params['age']
self.sex = params['sex']
except KeyError:
pass
…後者のやり方は完全に馬鹿げていますが、スキルがない人が書いたコード量の多いソースで見かけることがあります。このように、辞書型オブジェクトを直接参照する方法は、無駄なバリデーションや例外処理を必要とする場合が出てきてしまいます。
2. getメソッドを使う
それではgetメソッドを使って辞書型オブジェクトの値を参照するとどのようになるのでしょうか。
class SampleDict2:
def __init__(self, params):
self.name = params.get('name')
self.age = params.get('age')
self.sex = params.get('sex')
インスタンスを作成してみます。
from sample_class import SampleDict2
params = {'name':'test',
'age':38,
'sex':'male'}
sample_dict2 = SampleDict2(params)
print(sample_dict2.__dict__)
# {'name': 'test', 'age': 38, 'sex': 'male'}
パラメータに不足ない場合は1の場合と同様に正常に作成できました。それではパラメータが足りない場合はどうなるのでしょうか。
from sample_class import SampleDict1
params = {'name':'test',
'age':38}
sample_dict1 = SampleDict1(params)
print(sample_dict2.__dict__)
# {'name': 'test', 'age': 38, 'sex': None}
このように、KeyErrorにはならずにNoneが格納されました。これであれば、sexパラメータを必須としないなどの場合に、追加的な処理を必要としません。また、無駄なバリデーションや例外処理がないことによって可読性を損いません。
さらに、sexパラメータに何も値がないときは'unknown'としたい場合でも、1の場合よりもすっきりと書くことができます。
まず、1の直接参照する場合は以下のように実装するでしょう。
class SampleDict1:
def __init__(self, params: dict):
self.name = params['name']
self.age = params['age']
if 'sex' in params.keys():
self.sex = params['sex']
else:
self.sex = 'unknown'
一方、2のgetメソッドを使う場合は以下のように実装できます。
class SampleDict2:
def __init__(self, params):
self.name = params.get('name')
self.age = params.get('age')
self.sex = params.get('sex', 'unknown')
# dict.get(key[, default])
# Return the value for key if key is in the dictionary, else default.
このように、デフォルトの値を設定する際に、1の直接参照する方法だと4行使用するのに対して、2のgetメソッドを使う場合だと1行で書くことができます。
おわりに
以上で見てきたように、辞書型のオブジェクトの値を参照する際に、1の直接参照する方法よりも、2のgetメソッドを使う方が便利です。なので、直接参照しているコード書いていた方は今すぐにそのコードをすべてgetメソッドに変えてください(切実に)。
補足
最後の例について、一応1の場合でも以下のように書くことはできますがおすすめはしません。
1行が長くなり、PEP8で定められてる80行未満ルールに違反しやすくなるのと、それを回避するのに\を挟んで改行すると恐ろしく可読性と保守性が悪くなるからです。これをするくらいならまだ4行に分けた方がましだと思います。
class SampleDict1:
def __init__(self, params: dict):
self.name = params['name']
self.age = params['age']
self.sex = params['sex'] if 'sex' in params.keys() else 'unknown'