Works by ...

プログラミング関連でメモする

【python】デコレータ

pythonにはデコレータ構文が標準で実装されている。デコレータを使うことで、既存の関数に対し簡単に別の機能を付加(デコレート)することができる。


例えば、以下の様なコードを書いたとする。

def cake():
    print("cake")

cake()

とくれば、もちろん出力は

cake

となる。

次に、新たな関数を以下のように書き加えてみる。

def chocolate(func):
    print("chocolate")
    func()

@chocolate
def cake():
    print("cake")

cake

出力:

chocolate
cake

出力結果を見ると、cake()の出力の前に呼び出してもいないchocolate()の出力が表示されている。@chocolateをくっつけたcakeを呼び出しただけで、 chocolate()の提供する機能を実行している。これがデコレータ。

もっと言うと、変更後コード中の関数呼び出しcakeは以下と等価とのこと。

cake = chocolate(cake)

すなわち、変数cakeに、関数オブジェクトchocolateが同じく関数オブジェクトであるcakeを引数に取りつつ格納されているとみなせる。cake()呼び出し時に括弧が外れていたのはそのため。



また当然ながら、chocolate()内部で引数に取る関数の呼びたしタイミングは任意に決めることができる。

def chocolate(func):
    
    print("chocolate")
    

@chocolate
def cake():
    print("cake")

cake

出力:

cake
chocolate



ネストも可能。

def jam(func):
    print("jam")
    return func

def chocolate(func):
    print("chocolate")
    return func

@jam
@chocolate
def cake():
    print("cake")

cake()

デコレータ用の関数で関数を返戻しているのは、ネストした関数を正しく呼び出すため。

例えば以下のようにすると、

def jam(func):
    print("jam")
    func()

def chocolate(func):
    print("chocolate")
    func()

@jam
@chocolate
def cake():
    print("cake")

cake

以下の様なエラーを吐く。

Traceback (most recent call last):
chocolate
  File "/Users/.../Test/calc/__init__.py", line 14, in <module>
cake
    @chocolate
jam
  File "/Users/.../Test/calc/__init__.py", line 7, in jam
    func()
TypeError: 'NoneType' object is not callable

当然といえば当然で、この時のcakeは

cake = jam(chocolate(cake))

と等価で、中の

chocolate(cake)

は関数呼び出しであり、ひと通りchocolate()が実行されたあともその返戻値はNoneTypeである。したがって「NoneTypeは関数呼び出しできませんよー」と怒られている。