任意の関数にmiddleware的な何かを追加する方法

memo: 任意の関数にmiddleware的な何かを追加する方法。

例えばこういうような使い方。元となる関数fがある。

def f(x):
    return x * x

これを以下のような呼び出し関係になるfoo,barで包んだnew_fを作りたい。

begin foo
  begin bar
    f()
  end bar
end foo

ポイントとしては、以下の2つが存在して欲しい。

  • 元となる関数の呼び出し前のhook
  • 元となる関数の呼び出し後のhook

このようなhookの利用の仕方は以下のようになる。

def foo_middleware(context, create):
    # 呼び出し前のhook
    # do_something()

    # createが呼ばれる = 内部の関数が呼ばれる
    value = create(context)

    # 呼び出し後のhook
    # do_something(value)
    return value

middlewareはcontextという何か設定値を共有できる辞書と元の関数呼び出しを行うclosureを引数として取る関数。 ちなみに内部の関数に呼び出しをスキップして結果を返したい場合にはこのclosureを呼ばずにreturnすれば良い。

全体とつなげた使い方は以下の様になる。MiddlewareApplicatorが必要となるクラス

if __name__ == "__main__":
    # target function
    def f(x):
        return x * x

    def foo_middleware(context, create):
        print("foo: before create (context={})".format(context))
        context["marker"] = 1
        value = create(context)
        print("foo: after create (value={}, context={})".format(value, context))
        return value

    def bar_middleware(context, create):
        print("bar: before create (context={})".format(context))
        context["marker"] += 1
        value = create(context)
        print("bar: after create (value={}, context={})".format(value, context))
        return value

    new_f = MiddlewareApplicator([foo_middleware, bar_middleware])(f)
    print(new_f(10))

以下のような結果を返す。

foo: before create (context={'_keys': [], '_args': (10,)})
bar: before create (context={'_keys': [], 'marker': 1, '_args': (10,)})
bar: after create (value=100, context={'_keys': [], 'marker': 2, '_args': (10,)})
foo: after create (value=100, context={'_keys': [], 'marker': 2, '_args': (10,)})
100

gist(実装)

add_middleware.py · GitHub