Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

missing

【Python入門】Pythonの辞書とその特殊メソッド「__missing__」について基本をわかりやすく解説

Pythonの「__missing__」メソッドの基本的な使い方をわかりやすく解説

今回はPythonのマッピングオブジェクトである辞書型オブジェクトと、それに関係する特殊メソッドの「__missing__()」メソッドについて、その基本的な考え方と使い方を解説する記事です。

今回の解説では、

  • 辞書型オブジェクトを作るクラスであるdictクラス(class dict)と、実際にそれから作られた辞書型オブジェクトとの関係
  • クラスとサブクラスの関係
  • 辞書のキーの働き
  • __missing__メソッドの働き

などについてちょっとだけ理解が深まると思います。ぜひ読んでみてください。

Pythonにおける特殊メソッドとは?

Pythonにおける特殊メソッドとは、あるクラスの中で定義されるメソッドで、そのメソッドの名前にアンダーバーを2つ「__」セットにしたものが名前の前後についているメソッドのことです。たとえば次の「__aaaaa__」というメソッドがそれです。

class A:

    def __aaaaa__(self,var1, var2):
        (略)
    def aa(self):
        (略)

特殊メソッド「__missing__()」の基本的考え方と使い方

公式ドキュメントでの定義

さて、__missing__メソッドですが、これは公式ドキュメントでの説明では次のように書かれています。

object.__missing__(selfkey)

self[key] の実装において辞書内にキーが存在しなかった場合に、 dict のサブクラスのために dict.__getitem__() によって呼び出されます。

はい、いつもどおり安定して初心者には意味不明な文章ですね。

辞書のキーと例外のKeyError

では、次に公式ドキュメントのマッピング型の項目にある以下の記述を見てください。

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__メソッドについて見ていきましょう。

辞書のキーと__missing__メソッド

再び、上述d[key]項目の以下の部分を見てください。

辞書のサブクラスが __missing__() メソッドを定義していて、 key が存在しない場合、 d[key] 演算はこのメソッドをキー key を引数として呼び出します。 d[key] 演算は、 __missing__(key) の呼び出しによって返された値をそのまま返すか、送出されたものをそのまま送出します。他の演算やメソッドは __missing__() を呼び出しません。 __missing__() が定義されていない場合、 KeyError が送出されます。 __missing__() はメソッドでなければならず、インスタンス変数であってはなりません:

ここで重要なのは次の3点です。

  • (条件a)辞書のサブクラスが__missing__()メソッドを定義している
  • (条件b)もともと存在していないキー情報が与えられている
  • 条件aとbをどちらも満たす場合に、d[key]は、定義された__missing__()を、その仮引数へd[key]のkeyを設定して、__missing__(key)として呼び出す

ということです。

では実際に具体例を見ていきましょう。まず辞書を作るクラスとしての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):

には、

  • 第1仮引数self=new_dictオブジェクトそのもの
  • 第2仮引数dict_key=”Country”という文字列

がそれぞれ入ります。

その結果として、辞書new_dictについて、Countryという存在していないキーに、文字列USAという値が返ってきています。

さて最後にもう1つ重要なことを確認しておきましょう。それは、new_dictの中身が最初の状態から変化したのかどうか?です。

print(new_dict)

結果は、

{'Name': 'Mary', 'ID': 1023}

となり、最初の状態のままであるとわかります。つまり、サブクラスに__missing__()メソッドを定義し、処理の内容として存在していないキーに対応している値を返すようにしても、

  • 新しいキー(今回はCountry)
  • 新しい値(今回はUSA)

もともとの辞書(今回のselfに入るnew_dict)に追加されるわけではないとわかります。(もちろんそうするようにコードを書き換えることもできますが)

以上が__missing__()メソッドの基本的な考え方と使い方となります。

str.format_map(mapping)について

さてこのような__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の動画講座がわかりやすくおすすめです。