sankantsuのブログ

技術メモ・競プロなど

Python の decorator

概要

本記事では,python の decorator について

  • decorator の基本
  • syntax sugar
  • 引数をとる decorator

についてまとめた.

decorator の基本

decorator とは,関数を引数にとってその関数の動作になんらかの変更を加えた新しい関数を返すものである.

簡単な例を下に挙げる.

def decorate(func):
    def wrapper():
        print('**********')
        func()
        print('**********')
    return wrapper

def say_hello():
    print('Hello!')

say_hello = decorate(say_hello)

say_hello()

出力

**********
Hello!
**********

decorateという関数が,引数なしの関数funcを受け取る decorator である. decorate(say_hello)のようにsay_helloという関数を decorator に渡すことで,say_hello の実行前後に'**********'という行を出力するように加工した関数が返ってきている.

syntax sugar

def say_hello():
    print('Hello!')

say_hello = decorate(say_hello)

という書き方は同じ関数名を何度も書いていて,少しくどい. また,関数を加工する部分が関数定義箇所から離れてしまうので,可読性も低い.

そこで,syntax sugar として次の書き方が使える.

@decorate
def say_hello():
    print('Hello!')

これは効果としては上にあげた書き方と等価である. @によるsyntax sugar を用いたほうが,簡潔かつわかりやすく書ける.

引数を受け取る decorator

上の例で,********** が表示される行数を decorator に与える引数によって制御したいとする. つまり,次のような使い方ができるようにしたい.

@decorate_multi_line(num_lines=3)
def say_hello():
    print('Hello!')

say_hello()

出力

**********
**********
**********
Hello!
**********
**********
**********

これを行うためには,decorator_multi_line に出力行数のパラメータ num_linesを与えると,「関数の実行前後にnum_lines行だけ**********を出力するように加工する decorator 」を返すようにすれば良い.

具体的には,次のように実装できる.

def decorate_multi_line(num_lines):
    def decorate(func):
        def helper():
            for i in range(num_lines):
                print('**********')
        def wrapper():
            helper()
            func()
            helper()
        return wrapper
    return decorate

decorate_multi_line(num_lines=3)という呼び出しによって,3行ずつ装飾行を加えるdecorateがつくられる. ここで返ってきたdecorateがさらにsay_helloなどの関数に適用されることで,期待通りの動作が実現される.