Pythonで開発をしていると、関数の前後に共通処理を挟みたくなるケースが良くあるかと思います。その場合に良く使われるのはデコレータ(関数の上の行にアノテーションを付けるやつ)ではないでしょうか。この記事では、デコレータの基本的な使い方と、後半では変数を渡せるタイプのデコレータについて考察してみたいと思います。
※本記事の動作検証には、Pythonのバージョン3.7.3を使用しています。
一番シンプルなデコレータは、以下のような実装です。
def decorator(func):
def decorated(*args, **kwargs):
print('decorated 開始')
func(*args, **kwargs)
print('decorated 終了')
return decorated
このデコレータを実際に使う場合には以下のようにします。
@decorator
def sample(name):
print('{}が動作しました。'.format(name))
このデコレートされたsample関数を実行すると、以下のようにprint出力される事から
期待通りにsample関数の前後に処理を挟めている事が分かります。
decorated 開始
sampleが動作しました。
decorated 終了
何故このような動作になるかと言うと、@はシンタックスシュガーで
実は以下のコードと等価だからです。
def sample(name):
print('{}が動作しました。'.format(name))
# @decoratorと等価
sample = decorator(sample)
ここまでは比較的分かりやすいのですが、変数を渡せるタイプのデコレータを理解しようとした時に、つまづくケースが多いのではないかと思います。
※以下に、変数を渡せるタイプのデコレータの実装例を記載します。
def annotation(param):
print('annotation 開始')
def decorator(func):
print('decorator 開始')
print('param:{}'.format(param))
def decorated(*args, **kwargs):
print('decorated 開始')
print('param:{}'.format(param))
func(*args, **kwargs)
print('decorated 終了')
print('decorator 終了')
return decorated
print('annotation 終了')
return decorator
先ほどのシンタックスシュガー(@)の動きを考えると、最上位の関数(annotation)の引数は関数じゃないといけないのではないか?と思いがちですが、実際に下記のコードは正常に動作します。
print('アノテーション付きの関数を定義')
@annotation(param=999)
def sample(name):
print('{}が動作しました。'.format(name))
print('アノテーション付きの関数を実行')
sample('sample')
以下にprint出力を記載
アノテーション付きの関数を定義
annotation 開始
annotation 終了
decorator 開始
param:999
decorator 終了
アノテーション付きの関数を実行
decorated 開始
param:999
sampleが動作しました。
decorated 終了
上記のprint出力を見ると、annotation関数終了後に呼び出されているdecorator関数の中から
annotation関数の引数であるparamが参照出来ています。
何故このような挙動になるのかと言うと、関数が参照出来る変数は関数を定義した時に決まるので、annotation関数内で定義されたdecorator関数は、annotation関数の引数であるparamを参照する事が出来るからです。
※このようなオブジェクトの事をクロージャと言います。
同様に、decorated関数もクロージャになっている為、paramとfuncを参照する事が出来ます。
また、print出力の内容を考察すると、変数を渡せるタイプのデコレータのシンタックスシュガー(@)は、以下と等価だと考えられます。
def sample(name):
print('{}が動作しました。'.format(name))
# @annotation(param=999)と等価
sample = annotation(param=999)(sample)