Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
今回はPythonのマッピングオブジェクトである辞書型オブジェクトと、それに関係する特殊メソッドの「__missing__()」メソッドについて、その基本的な考え方と使い方を解説する記事です。
今回の解説では、
などについてちょっとだけ理解が深まると思います。ぜひ読んでみてください。
Contents
Pythonにおける特殊メソッドとは、あるクラスの中で定義されるメソッドで、そのメソッドの名前にアンダーバーを2つ「__」セットにしたものが名前の前後についているメソッドのことです。たとえば次の「__aaaaa__」というメソッドがそれです。
class A:
def __aaaaa__(self,var1, var2):
(略)
def aa(self):
(略)
さて、__missing__メソッドですが、これは公式ドキュメントでの説明では次のように書かれています。
object.__missing__(self, key)¶
self[key] の実装において辞書内にキーが存在しなかった場合に、 dict のサブクラスのために dict.__getitem__() によって呼び出されます。
はい、いつもどおり安定して初心者には意味不明な文章ですね。
では、次に公式ドキュメントのマッピング型の項目にある以下の記述を見てください。
d[key]
d のキー key の項目を返します。マップに key が存在しなければ、 KeyError を送出します。辞書のサブクラスが __issing__() メソッドを定義していて、 key が存在しない場合、 d[key] 演算はこのメソッドをキー key を引数として呼び出します。 d[key] 演算は、 __missing__(key) の呼び出しによって返された値をそのまま返すか、送出されたものをそのまま送出します。他の演算やメソッドは __missing__() を呼び出しません。 __missing__() が定義されていない場合、 KeyError が送出されます。 __missing__() はメソッドでなければならず、インスタンス変数であってはなりません:
こちらの内容を確認していきましょう。まずテキトーに辞書を1つ作ります。今回はprofile_dictという名前にしてみました。
# 辞書は、{key: value}という形式
profile_dict = {'Name': 'Mary', "ID": 1023,}
もちろん型を調べると、
type(profile_dict) # ==> <class 'dict'>
結果は、
<class 'dict'>
となって、クラスdictから作られた辞書型だとわかります。
次にdir()関数使ってprofile_dictが持っているメソッドや属性(アトリビュート)を調べてみると、
dir(profile_dict)
結果は、
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
となります。
ではこの辞書のキー(Key)情報を使ってみましょう。
profile_dict["Name"] # ==> 'Mary'
profile_dict["ID"] # ==> 1023
結果は、
'Mary'
1023
となります。ここで上の公式ドキュメントのd[key]の項目を再びみてみましょう。
d[key]
d のキー key の項目を返します。マップに key が存在しなければ、 KeyError を送出します。
このように書かれていました。これを確かめて見ましょう。キー(key)には、最初に定義していないCountryというキーを設定します。
profile_dict["Country"]
結果は次のようにKeyErrorという種類の例外が発生します。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Country'
もともとの辞書にはCountryというキーは存在していないので、まさに説明どおりKeyErrorが送出されています。
ここまでは大丈夫ですね?ではいよいよ今日の本丸、__missing__メソッドについて見ていきましょう。
再び、上述d[key]項目の以下の部分を見てください。
辞書のサブクラスが __missing__() メソッドを定義していて、 key が存在しない場合、 d[key] 演算はこのメソッドをキー key を引数として呼び出します。 d[key] 演算は、 __missing__(key) の呼び出しによって返された値をそのまま返すか、送出されたものをそのまま送出します。他の演算やメソッドは __missing__() を呼び出しません。 __missing__() が定義されていない場合、 KeyError が送出されます。 __missing__() はメソッドでなければならず、インスタンス変数であってはなりません:
ここで重要なのは次の3点です。
ということです。
では実際に具体例を見ていきましょう。まず辞書を作るクラスとしてのdictクラスを継承したサブクラスを作ります。サブクラスの名前はSubDictにしました。
#dictクラスを継承したサブクラスを定義
class SubDict(dict):
def __missing__(self, dict_key):
return "USA"
そして上のようにメソッドとして、__missing__()を定義しました。
new_dict = SubDict(profile_dict)
そのサブクラスSubDictから、上で作った辞書profile_dictを利用して、インスタンスオブジェクトnew_dictを作りました(実際にはPythonの代入文は、右辺においてSubDictからオブジェクトを作成→そのオブジェクトに左辺の名前をつけるだけです)。
このnew_dictの中身を見ると、
print(new_dict)
結果は、
{'Name': 'Mary', 'ID': 1023}
上で作った辞書profile_dictと同じ内容の辞書になっています。
ではそのnew_dictの型を調べてみましょう。
type(new_dict)
結果は、
<class '__main__.SubDict'>
上のprofile_dictの場合は、dir(profile_dict)の結果は、「<class ‘dict’>」。これはクラスdictから作られたオブジェクトだということです。しかし、今回はあくまでサブクラスであるSubDictから作ったインスタンスオブジェクトなので、その型はSubDictとなります。
次にこのnew_dictのメソッドなどをチェックしましょう。
dir(new_dict)
結果は、
['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__missing__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
ここに、「__missing__」というメソッドが存在していることに気づいてください。先ほどの上で、
dir(profile_dict)
とした場合は、__missing__メソッドは存在していませんでした。なぜなら、親クラス(スーパークラス)であるもともとのクラスdictにはそれが定義されていないからです。
しかし今回のコードではdictクラスを継承したサブクラスSubDictにおいて、特殊メソッドとして__missing__メソッドを定義しており、そのSubDictによって作られたオブジェクトがnew_dictなので、SubDict内で定義されたメソッドを有しているわけです。
このように親クラス(スーパークラス)が持っていないメソッドや属性(アトリビュート)をサブクラスで作り出すことができます。親クラスを拡張・進化させてしているという感じですね。
ではいよいよ、new_dictにそもそも存在していないキーを与えて、その値を取得してみましょう。
new_dict["Country"]
結果は、
'USA'
となります。もともと存在していないキーにでしたが、USAという文字列が値として返ってきました。
一方で上述のprofile_dictの場合はもともと定義されていないキーに対応する値を取得しようとすると、KeyErrorが返ってきました。
しかし、今回は特殊メソッドとして__missing__()メソッドを定義しましたので、存在しないキーが指定されると、__missing__()メソッドが呼び出されることになります。そして、
def __missing__(self, dict_key):
には、
がそれぞれ入ります。
その結果として、辞書new_dictについて、Countryという存在していないキーに、文字列USAという値が返ってきています。
さて最後にもう1つ重要なことを確認しておきましょう。それは、new_dictの中身が最初の状態から変化したのかどうか?です。
print(new_dict)
結果は、
{'Name': 'Mary', 'ID': 1023}
となり、最初の状態のままであるとわかります。つまり、サブクラスに__missing__()メソッドを定義し、処理の内容として存在していないキーに対応している値を返すようにしても、
がもともとの辞書(今回のselfに入るnew_dict)に追加されるわけではないとわかります。(もちろんそうするようにコードを書き換えることもできますが)
以上が__missing__()メソッドの基本的な考え方と使い方となります。
さてこのような__missing__()メソッドの基本がわかると、文字列オブジェクトのformat_mapメソッドの使い方について公式ドキュメントが書いている説明もわかりやすくなると思います。
そこでは次のようなコードが紹介されています。
class Default(dict):
def __missing__(self, key):
return key
'{name} was born in {country}'.format_map(Default(name='Guido'))
これを実行すると結果は、
'Guido was born in country'
となります。
こうしたPythonのクラスやメソッドなどの基本的文法については、次のようなUdemyの動画講座がわかりやすくおすすめです。