3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonの関数デコレーター

Last updated at Posted at 2020-04-07

調べ物のまとめと復習用の教材としてQiitaに残していくことにしました。
自分の整理用なので不親切な記載も多いかと思います。
その場合は、参考URL一覧へのリンク集としてご利用ください。

由来

もともと、デザインパターンにはデコレーターパターンというものがあり、Pythonのデコレーターもそれから来ているようです。

デコレーターパターン

既存のオブジェクトに新しい機能や振る舞いを動的に追加すること

引用元:Decorator パターンhttps://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

こちらの定義を前提に理解を進めていきます。
ざっくりいうと、実行したい処理に対して、前処理や後処理を加えたり、レスポンスそのものをすり替えたりといったことができます。

厳密にいうとDecoratorパターンとDecoratorは別物、ないしは後者が前者の要素にあたるようですが、そこは深堀りしない。
参考:python-3 patterns,idioms and test
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html

ファーストオブジェクト

Pythonでは関数はファーストオブジェクトです。Pythonの関数はオブジェクトであり、関数の引数や戻り値に関数を利用することが出来ます。デコレーターは引数として受け取った関数を、デコレーター自身の処理の一部として実行することができます。

動作確認

下記のようなことをして遊んでました。

decorater.py
# デコレーターを定義する
def decorator(func):
    # 名前はdecoじゃなくてもいい。dededeとかでもいい。
    def _deco(*args, **kwargs):
        # 関数の名前はなんでもいい。decodecoとかでもいい。
        print('japan')
        func()
        print('america')
    return _decorator

# @をつけてデコレーターと関数を紐付ける
# デコレーターがdededeなら@dededeとかつける
@decorator
def hello_world():
    print('china')
# japan
# china
# america

@decorator
def hello_earth():
    print('england')
# japan
# england
# america

@decorator
def hello_tokyo():
    print('tokyo')
# japan
# tokyo
# america

各関数はdecorator関数の引数として渡され、デコレーター内部で実行されます。
@decoratorを先頭につけた関数がfuncとしてデコレーターの引数になります。
デコレーターの名前はなんでもいいです。用途にあわせて適切な名前をつけましょう。
上記の例では、japan, americaでprint処理を挟み込むことができました。

実装例

動作確認ができたので実装です。API Gateway + LambdaでよくあるAPIを実装しました。
実際のところは、私じゃない人が設計・実装してるので、要素の一部を簡易に記載します。
lambdaの処理に対して、リクエストパラメーターの前処理とログ処理を行います。

implementation.py
    def implementation(func):
        def _deco(*args, **kwargs):
            # *argsはlambdaのeventを受け取れる。*argsは可変長引数。

            print("lambda start")
            print(args[0])
            # ログ用の処理は別途作成して呼び出すと使いやすい。ここでは仮なのでprint()

            params = json.loads(event.get("body"))
            if event.get('pathParameters'):
                params.update(event.get('pathParameters'))
            # get,postそれぞれの処理をひとつのparamsにまとめる。

            if not params.get("id"):
                return error_response()
            # パラメーターにIDがない場合、エラーを返すといった処理ができる。

            response = func(params, id)
            # 個別処理の実行結果を取得する
            print("lamdba end")
            return response
            # ネストの最下層に戻り値を設定するとそれが新しい返り値にすり替わる。
            # 本来の処理のレスポンスを返却する前にログの吐き出しを行える。
        return _deco
lambda_function.py
@implementation
def handler(params, id):
    """
    何かしらの処理
    """
   return success_response()

本来の処理を実行する際に、ログを共通の処理として仕込んだり、パラメーターの整理や要素を確認してレスポンスをエラー時のものにすり替えたり、といった機能を追加することが出来ます。

個別の処理と紐付けるために引数付きのデコレーター

たとえば、バリデーションをする場合は、個別の処理に応じて求める値が変わって来ます。
そこで、個別処理で定義したバリデーション用のスキーマを引数に渡せるようにするには、引数付きのデコレーターを利用します。

implementation_with_params
def implementation_with_params(schema):
    def _deco(func):
        def _decodeco(*args, **kwargs):
            params = *args[0].get("body")
            error = validator(schema, params)
            # なにかしらのバリデーターを定義した関数に渡す。
            if error:
                return error_response()
            response = func(params)
            return response
        return _decodeco
    return _deco
lambda_function.py
# バリデーションするためのキーの一覧
SCHEMA = ["id","user_name", "password", "address", "tel"]

@implementation_with_params(schema=SCHEMA)
def handler(params)
    """
    何かしらの処理
    """
    return success_response()

実際のところはCerberousでバリデーションをしています。

他の用途

レスポンス内容の変更、リクエストパラメーターの前処理とログ処理、さらにしれっとバリデーション処理も記載してますが、他にも下記のような用途が考えられます。
・処理の開始と終了を記録してパフォーマンス測定
・認証処理
・セッション管理

参考URL

公式ドキュメント
https://docs.python.org/ja/3/reference/compound_stmts.html#function
Pythonのデコレータについて
https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e
Pythonのデコレータを理解するための12Step
https://qiita.com/_rdtr/items/d3bc1a8d4b7eb375c368
Python デコレータ再入門  ~デコレータは種類別に覚えよう~
https://qiita.com/macinjoke/items/1be6cf0f1f238b5ba01b
引数付きデコレータの作り方
https://qiita.com/utgwkk/items/d7c6af97b81df9483f9e
Python再発見 - デコレータとその活用パターン -
https://slideship.com/users/@rhoboro/presentations/2017/12/YagWLokYuWX5Er78UdLbWX/?p=23
Decorator パターン
https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
デザインパターン(Design Pattern)#Decorator
https://qiita.com/nirperm/items/398cb970826fa972a94f
python-3 patterns,idioms and test
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html
Pythonにおけるデザインパターン ~Pyデザパタ~ Decoratorパターン
https://pydp.info/GoF_dp/structure/09_Decorator/

余談

JavascriptとTypescriptにもデコレーターってあるんですね。

JavaScript の デコレータ の使い方
https://qiita.com/kerupani129/items/2b3f2cba195c0705b2e5
TypeScriptのDecoratorについて – 公式ドキュメント日本語訳
https://mae.chab.in/archives/59845
きれいで読みやすいJavaScriptを書く デコレーターの基本を先取りhttps://www.webprofessional.jp/javascript-decorators-what-they-are/

3
4
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
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?