2.23. デコレータ

デコレータ とは、関数(またはメソッド)を別の関数でラップする構文です。ラッパーは、元の呼び出しの前後に動作を追加したり、戻り値を置き換えたり、メタデータを付加したりできます。その形は次のとおりです。

@wrapper
def f():
    ...

@wrapper の行は f = wrapper(f) と同等です -- 関数 f は通常どおり構築され、その後 wrapper に渡され、その結果が名前 f に再束縛されます。

関数が左側で「ラッパー」のボックスに入り、 右側でデコレートされた関数として出てくる様子。

デコレータは関数を受け取り、新しい関数を返します。

2.23.1. 組み込みのメソッドデコレータ

いくつかのデコレータは Python に付属しており、クラス本体の中で使用されます。

2.23.1.1. @property

メソッドを 計算される属性 に変えます。呼び出し側は(括弧なしで)あたかも素の属性であるかのようにアクセスしますが、読み取るたびにメソッドが実行されます。

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

見た目 は単純な属性が、その背後でちょっとした計算を必要とする場合に使用してください。処理が高価な場合は、通常のメソッドのほうが望ましいです -- 呼び出し側は属性の読み取りが遅いとは思っていません。

2.23.1.2. @classmethod

インスタンスの代わりに クラス を最初の引数として受け取るメソッドを定義します。最初のパラメータは慣例的に cls と名付けられます。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

クラスメソッドは、代替コンストラクタ を提供する標準的な方法です -- デフォルトとは異なる方法で構築されたインスタンスを返すファクトリ関数です。

2.23.1.3. @staticmethod

インスタンスもクラスも受け取らないメソッドを定義します -- それは整理上の理由でクラスの名前空間に置かれた、単なる素の関数にすぎません。

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

使いすぎないようにしてください。関数が本当にクラスの状態と何の関係もないなら、通常はモジュールレベルの素の関数のほうがすっきりします。

2.23.2. カスタムデコレータを書く

デコレータは、関数を受け取って関数を返す関数です。最小限の形は次のとおりです。

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

出力:

calling add

wrapperfunc をクロージャに取り込み、すべてをそれに転送します。*args / **kwargs により、2 引数の関数だけでなく任意の関数で動作できます。このパターンは、より手の込んだデコレータ(計測、キャッシュ、失敗時の再試行)の基礎ですが、核心は常に同じです。関数を受け取り、関数を返すのです。