[Python入門]デコレーターの基礎:Python入門(2/2 ページ)
デコレーターを使うと、関数に何らかの追加機能を持たせられる。デコレーターの定義や、その使い方、同じことをするPythonコードについて見ていこう。
引数を持つデコレーター
次にパラメーターを持つデコレーターについても見てみよう。つまり、以下のような書き方ができるデコレーターだ。ちょっとした理由からデコレーターの名前をここでは「f1」としよう。
@f1('foo')
def some_func(msg):
print(msg)
このときにf1関数の定義はどうなるだろうか。実は、パラメーターを持つデコレーターの定義はちょっと面倒なことになる。実際のコードをご覧いただこう。
def f1(msg='foo'):
def inner_func(func):
def most_inner_func(*arg):
print(f'msg: {msg}')
print('most_inner_func')
result = func(*arg)
return most_inner_func
return inner_func
どういうことか分かるだろうか。最も外側のf1関数では、デコレーターが受け取るパラメーターだけを受け取り、その内部で、関数を受け取り関数を返すinner_fun関数(デコレーター)を返送している。そして、inner_func関数の内部では、f1関数とinner_func関数がパラメーターに受け取った値を利用する関数を定義して、それを返送するようになっている。
関数定義がネストしているので少々分かりにくいが、パラメーターを持つデコレーターはこのようにして定義する。最初にデコレーターとは「関数を受け取り、関数を戻り値とする関数」と述べたが、パラメーターを持たせる場合には、このように大外では関数を受け取らない代わりに、関数を受け取り、関数を戻り値とする関数を返すことで、同じ効果が得られる。
上にも示したが、このデコレーターを使用するには次のようなコードを書く。
@f1('bar')
def some_func(msg):
print(msg)
some_func('hello world')
これを実行すると次のようになる。most_inner_func関数内でf1関数とinner_func関数がパラメーターに受け取った値を利用している点に注目しよう。
では、これをデコレーター式を使わずに書くとどうなるかを考えてみよう。これには、先ほどと同様、some_func関数と同じ内容のother_func関数を定義して、それにf1関数をどう適用すればよいかを考えればよい。
def other_func(msg):
print(msg)
f1関数はパラメーターを1つ持つので、「f1('bar')」と書ける。注意したいのは、f1関数はデコレーターを返す点だ。デコレーターを関数に適用するには「デコレーター(関数)」のようにした。つまり、「f1('bar')」の戻り値であるデコレーターに「(関数)」を付加して、適用先の関数を渡せばよい。これをコードにすると「f1('bar')(other_func)」となる。これを変数other_funcに再代入すれば、デコレーター式と同じ結果が得られる。
other_func = f1('bar')(other_func)
other_func('hello world')
実行結果は省略するが、このコードを実行すると、上と同じ結果になる。
ここで、もうデコレーターをもう1つ定義しよう。名前は「f2」とする。実際に行っているのは、他のデコレーターと同じなのでコードの説明は省略する。
def f2(func):
def inner_func(*args):
print('decorator f2')
result = func(*args)
return result
return inner_func
ここでf1デコレーターとf2デコレーターを使って関数を定義し、それを呼び出してみよう。
@f1('arg')
@f2
def func():
print('func')
func()
実行結果は次のようになる。
ここで、デコレーター式による修飾を使わずに、上と同じことをしようと思ったらどうなるだろう。
重要なのは、先ほど見た「パラメーターを持つデコレーターはデコレーターを戻り値とする」ところだ。上のコードなら「f1('arg')」によりデコレーターが得られる。これをf0デコレーターとしよう。f0デコレーターを使うと、上のfunc関数定義は次のように書き換えられる。
f0 = f1('arg') # デコレーターを取得
@f0
@f2
def func():
print('func')
この形式の修飾がどういう意味かは既に見た。これは「func = f0(f2(func))」と同じ意味だった。そして、「f0」は「f1('arg')」だったので、これで「f0」を置き換えると「f1('arg')(f2(func))」が得られる。これが、先ほどのデコレーター式による修飾と同じ意味になる。
実際に試してみよう。
def func2():
print('func2')
func2 = f1('arg')(f2(func2))
print('--- calling func ---')
func()
print('--- calling func2 ---')
func2()
これを実行すると、次のようになる。
「f1」「f2」という名前を使ったのは、Pythonのドキュメント「関数定義」で使用されているからだ。
ここまでに見てきたサンプルコードで、上記ドキュメントで触れられているコード例が(引数やfunc関数定義の本体の内容などに差はあるが)ほぼ同一であり、上で得られた同じ意味のコードもドキュメントで記述されているものとほぼ同一になっている点に注目されたい。
ドキュメントでは、デコレーター式による関数定義の修飾と同じ意味になるコードの説明の際に、上で見たようなロジックの解説がなされていないので、ここではそこを補うために、デコレーターの名前を同一のものとした。
まとめ
今回はPythonのデコレーターの基礎について見た。実際にはデコレーターを使用することで、例えばログ出力機能を関数に持たせるといったことを簡単に実現できる。これらについては機会があれば紹介しよう。
今回のまとめ
- デコレーターとは、関数を受け取り、関数を戻り値とする関数のこと
- デコレーターを使うには、関数定義(やクラス定義)の前に「@デコレーター」を付ける(修飾する)
- デコレーターは、受け取った関数に何らかの処理を施して、それを戻り値とする
- デコレーターとして機能する関数は、「@デコレーター」のようにデコレーター式として関数定義を修飾できる
- デコレーター式で修飾された関数定義は「デコレーター(関数)」と同じ意味になる
- デコレーターにパラメーターを持たせるには、大外の関数でパラメーターを受け取り、それが関数を受け取り、関数を戻り値とする関数(デコレーター)を戻り値にする
- パラメーターを持つデコレーター式で修飾された関数定義は「デコレーター(引数)(関数)」と同じ意味になる
- 複数のデコレーター式で修飾された関数定義は「デコレーター1(デコレーター2(関数))」と同じ意味になる
- パラメーターを持つデコレーターのときには、「大外のデコレーター(引数)(関数や他のデコレーター)」のような意味になる
Copyright© Digital Advantage Corp. All Rights Reserved.