Python のdecorator について エキスパートPython プログラミングのメモ

競プロ関係ではないんですが、勉強していてわからないところがあったんでメモしておきます。

エキスパートPythonプログラミングではデコレータ

def some_decorator(function):
  def wrapped(*args, **kargs):
    # 関数を呼び出す前に行う処理
    result = function(*args, **kargs)
    # 呼び出し後に行う処理
    return(result)
  # 引数にとったfunction をデコレートした関数を返す。
  return(wrapped)

@some_decorator
def decorated_function():
  pass

は以下のような関数再定義の構文糖衣であると説明されている。

def decorated_function():
  pass
decorated_function = some_decorator(decorated_function)

ここでパラメータ付きのデコレータ

def repeat(number=3):
  def actual_decorator(function):
    def wrapper(*args, **kargs):
      result = None
      for _ in range(number):
        result = function(*args, **kargs)
      return(result)
    return(wrapper)
  return(actural_decorator)

を考える。repeatそのものはデコレータではなく、パラメータを受け取り、デコレータactual_decorator を返す関数である。このときacutual_decoratorrepeat が受け取るパラメータによって変更されている。

実際のデコレータはrepeat そのものではなくこれが返す関数なのであるから、デコレートされる関数を定義するときは、@repeat ではなく@repeat(x) としなければいけない。デフォルトのパラメータ値を用いるときであっても@repeat() である。

@repeat()
def foo():
  print("foo")
>>> foo()
foo
foo
foo

以下のように() をつけずに作用させるとエラーになる。

# 失敗例
@repeat
def foo():
  print("foo")
>>> foo()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-260-624891b0d01a> in <module>()
----> 1 foo()
TypeError: actual_decorator() missing 1 required positional argument: 'function'

後者の例は以下の関数再定義と同義であるので、foo 自身がデコレータ(関数を受け取って修飾を施す関数)になってしまっている。

foo = repeat(foo) # foo の返り値はacutual_decorator
>>> ?foo
Signature: foo(function)