トップページに戻る

pythonのデコレータ

 pythonでは、@hogehoge というデコレータを関数 func の定義の前に置くことで、func()呼び出しに hogehoge() 関数で定義した動作を追加できます。この仕様をデコレータと呼びます。

 例えば次の例

def hogehoge(f): 
    def _wrapper(*args, **kwargs):
        print(f'hogehoge._wrapper(): デコレータが呼び出されました: args={args} kwargs={kwargs}')
        print(f'hogehoge._wrapper(): 関数 f を呼び出します')
        v = f(*args, **kwargs)
        print(f'hogehoge._wrapper(): 関数 f の戻り値: {v}') 
        return v

    return _wrapper

@hogehoge 
def func(*args, **kwargs): 
    print(f"funcが呼び出されました")
    print(f" args=", *args)
    print(f" kwargs=", kwargs)
    return 1.0


func(3.0, a = 'abc')

は、

hogehoge(func)(3.0, a = 'abc')

  # f = hogehoge(func)    # <= hogehoge._wrapper()関数の参照が f に入る
  # f(3.0, a = 'abc')    # <= hogehoge._wapper(*args, **kwargs) 関数が呼び出される。
                           # *args と **kwargsは func(3.0, a = 'abc') に渡された位置引数 3.0 と
                      # キーワード引数 {"a": 'abc'} に対応

あるいは

print(f'hogehoge._wrapper(): デコレータが呼び出されました: args={args} kwargs={kwargs}')
print(f'hogehoge._wrapper(): 関数 f を呼び出します')
v = func(3.0, a=  'abc')
print(f'hogehoge._wrapper(): 関数 f の戻り値: {v}') 
return v

と同じ動作をします(異なる書き方で同じ動作をすることを Syntax sugar であるといいます)。
つまり、hogehoge(func)は hogehoge._wrapper() 関数を返すので、
hogehoge(func)(3.0, a = 'abc') により hogehoge._wrapper(3.0, a = 'abc') が呼び出されます。 
hogehoge._wrapper(3.0, a = 'abc') 内ではfunc(3.0, a = 'abc')を呼び出していますが、その前後に追加処理を行っています。

 デコレータは、このような共通の追加処理を関数に追加するための仕組みです。


Flask、DashなどのWebフレームワークにおけるデコレータ

 これらのWebフレームワークの多くは、routingという機能を持っています。
url への HTTP request が発生したとき、そのurlに対応した関数を実行します。

 Flaskの場合はroutingのdecoratorはFlaskオブジェクトのroute(url)関数で定義されていますので、

app = Flask(__name__)

@app.route('/hello')
def hello():
   pass

とすることで、'/hello'へのrequestが発生したときに関数hello()を呼び出すことができます。

 Dashの場合は、Dashクラスで定義されている .callback() 関数がroutingを担います。


もう少し、decoratorの動作について補足します。

この部分は、Microsoft365 Copilotに、Flaskのapp.route()の動作機構について質問し、その回答をまとめたものです。
Flaskのソースコードを確認したものではありませんので、ご理解ください。

 routingの動作を理解するためには、python起動時からのdecoratorの動作を理解する必要があります。

 Flaskではデコレータapp.route()はFlask classで以下のように定義されています。

class Flask:
  def __init__(self):
    self.routes = {}

  def route(self, url):
    def decorator(func):
      self.routes[url] = func
      return func

  return decorator

def handle_request(self, url):
  if url in self.routes:
    return self.routes[url]()
  else:
    return "404 Not Found"

app = Flask()

@app.route('/hello')
def hello():
  print('Hello')

 python起動時にはまず、全てのdecorator定義が呼び出されます
これは、
def my_decorator(func):
  ...

@my_decorator
def my_function():
  ...
のとき、@my_decoratorは
my_function = my_decorator(my_function)
に等価であり、pythonスクリプトの読み込み時にこれが実行されるという仕様によります。
このことは、この処理によって、my_functionがdecorator内の_wrapper関数におきかえられるということがわかります。

 上のFlaskの場合は、スクリプト読み込み時に app.route('/hello') が実行されます。
app.route(url) のdecorator(func)関数には修飾しているhello関数が渡されますので、

      self.routes[url] = func

によって、urlと関数の関係が登録されます(routing)。

 その後、HTTP requestが発生すると、app.handle_request(url) が呼び出され、

    self.routes[url]()

によって hello() が呼び出されます。

 つまり、デコレータの動作としては、

の使い方があるようです。

 pythonスクリプト読み込み時にdecorator関数を実行したくない場合は、上記の@my_decoratorの動作のように、

my_function = my_decorator(my_function)

と記述することにより、decoratorと同じ動作をします。