LoginSignup
3
2

More than 3 years have passed since last update.

変数を渡すタイプのPythonのデコレータについての考察

Posted at

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)

参考資料

Pythonのクロージャについて: 関数のスコープと、関数が第一級オブジェクトであることからちゃんと考える

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2