デコレーターを使うと、関数に何らかの追加機能を持たせられる。デコレーターの定義や、その使い方、同じことをするPythonコードについて見ていこう。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
今回はPythonが提供するデコレーターの基礎について見ていく。
本連載の第15回「ローカル関数とラムダ式」では関数を受け取る関数や、関数を戻り値とする関数を取り上げた。今回紹介するデコレーターとは、基本的には関数を受け取り、関数を戻り値とする関数のことだ(そうではない場合もある。後述)。クラスを受け取り、クラスを戻り値とする関数やクラスも書けるが、本稿では関数を受け取り、関数を戻り値とする基本的なデコレーターだけを紹介する。
「関数を受け取り、関数を戻り値とする関数」というと難しく感じるかもしれないが、デコレーターの使用例は第29回「クラス変数/クラスメソッド/スタティックメソッド」の「クラスメソッド」で既に紹介している。デコレーターを使うだけなら、以下のように「@デコレーター」(デコレーター式)を使って関数を修飾(デコレート)するだけでよい(以下の例はメソッドだが、使い方は同じ)。
class MyClass:
# …… 省略 ……
@classmethod # クラスメソッドであることを示すデコレーター
def get_count(cls): # デコレーターで修飾されたメソッド(関数)
print(cls.count)
上のコードは、関数を受け取り、関数を戻りとする「classmethod」という名前の組み込み関数にその直下で定義しているget_count関数を渡していると考えられる。classmethod関数は、受け取ったget_count関数(メソッド)をクラスメソッドに変換するための処理をしてくれる。
ここまでの話をまとめると次のようになる。
簡単な例を以下に示す。
def mydecorator(func):
def inner_func(*args):
print(f'before execute {func.__name__}')
result = func(*args)
print(f'after execute {func.__name__}')
return result
return inner_func
mydecorator関数はパラメーターfuncに関数を受け取る。そして、内部で定義しているinner_func関数を戻り値とする。そのため、これはデコレーターとして使用できる。
では、inner_func関数では何をしているかについて考えよう。まず、この関数はパラメーター*argsに任意の数の値を受け取る。そして、「result = func(*args)」という行で、自らのパラメーター*argsに受け取った値を引数として、mydecorator関数がパラメーターfuncに受け取った関数を呼び出している。その前後では、メッセージを表示している(「func.__name__」というのは、パラメーターfuncに受け取った関数の名前だ)。
つまり、mydecorator関数は「受け取った関数を実行し、その前後にはメッセージを表示する関数」を戻り値とする。実際にこれを使ってみよう。まずはデコレーター式を使わずに、そのまま実行してみよう。
mylen = mydecorator(len) # 組み込みのlen関数をmydecorator関数に渡す
print(mylen('string'))
1行目では、変数mylenには、len関数を内部で実行(し、その前後にメッセージも表示)する関数が代入される。2行目では実際にその関数を呼び出している。「mylen('string')」は意味的には「inner_func('string')」である点に注意しよう(このとき、inner_func関数では、そのローカル変数funcに組み込みのlen関数が代入されている。これを確認したい人は、inner_func関数定義の本体の行目に「print(locals())」と書いてみよう)。
実行すると、次のようになる。
メッセージが表示され、文字列'string'の文字数が返されたので、その結果である「6」が表示された。
なお、「mydecorator(len)」呼び出しの結果が関数(関数オブジェクト)になるので、上のコードは次のように書いてもよい(分かりやすいように、print関数呼び出しは省略)。
mydecorator(len)('string')
今度はmydecorator関数を適用する関数も自分で作ってみよう。
def myfunc(msg):
print(msg)
myfunc = mydecorator(myfunc)
myfunc('hello world')
実行結果を以下に示す。
上のコードでは、myfunc関数を定義して、それをmydecorator関数に渡した結果を、同じ「myfunc」という変数(名前)に代入(束縛)している。これを簡略化して表記するのが、「@デコレーター」(デコレーター式)で使用した関数定義の修飾だ。つまり、「@デコレーター」を使うと、上のコードは次のように書ける。
@mydecorator
def myfunc(msg):
print(msg)
つまり、「@デコレーター」で関数を修飾するのは、上で見た「変数 = デコレーター(関数)」という記述と意味的には同じということだ。
次にデコレーターを2つ定義して、それらの使い方と意味するところについて考えてみよう。
def decorator1(func):
def inner_func(*args):
print('decorator 1')
result = func(*args)
return inner_func
def decorator2(func):
def inner_func(*args):
print('decorator 2')
result = func(*args)
return inner_func
デコレーターを複数適用するときの動作を調べることを目的とするので、ここでは2つのデコレーターは表示するメッセージが違うだけのものとしている。これらのデコレーターを使って、関数定義を修飾してみよう。
@decorator1
@decorator2
def myfunc(msg):
print(msg)
このようにして定義したmyfunc関数を呼び出すと、どんな結果になるだろうか。つまり、2つのメッセージはどんな順序で表示されるだろうか。それが分かれば、この記述に対応する、デコレーター式表記を使わないPythonコードが分かる。
myfunc('hello world')
実行結果は次のようになる。
読者の予想通りか、そうでないかは分からないが、「decorator 1」が最初に表示されて、次に「decorator 2」が表示され、最後にmyfuncのコードが実行されていることが分かった。
では、これに対応するデコレーター式を使わない表記はどうなるだろう。デコレーターで修飾をしていない同内容の関数を定義して、それらに順番にデコレーター関数を適用していってみよう。デコレーターが2つなので、それらが適用される順番は2通りだ。
def yourfunc(msg):
print(msg)
tmp1 = decorator2(yourfunc)
tmp2 = decorator1(tmp1)
tmp3 = decorator1(yourfunc)
tmp4 = decorator2(tmp3)
変数tmp1にはdecorator2関数にyourfunc関数を渡した結果(関数、デコレーター)が代入され、変数tmp2には変数tmp1に代入された関数をdecorator1関数に渡した結果(関数)が渡されている。変数tmp3と変数tmp4はその順番を変えたものだ。
簡単にいえば、変数tmp2は「decorator1(decorator2(yourfunc))」の結果が、変数tmp4には「decorator2(decorator1(yourfunc))」の結果が代入されていると考えられる。では、これらの関数を呼び出してみよう。
print('--- calling tmp2 ---')
tmp2('hello world')
print('--- calling tmp4 ---')
tmp4('hello world')
実行結果を以下に示す。
このことから、複数のデコレーターで関数定義を修飾すると、「デコレーター1(デコレーター2(関数))」のように、修飾時に上に記述したデコレーターの呼び出しに、下に記述したデコレーターが渡され、さらにそのデコレーターに修飾された関数が渡されていくことが分かった。
Copyright© Digital Advantage Corp. All Rights Reserved.