1. デコレータの基本
デコレータは、他の関数を引数として受け取り、それを「ラップ」する新しい関数を返す関数です。この「ラップ」された関数は、元の関数に何らかの追加機能を提供します。
ネスティングされた関数(クロージャ)
Pythonでは、関数内で別の関数を定義することができます。この内側の関数は、外側の関数のローカル変数にアクセスできるため、クロージャとして機能します。デコレータでは、このネスティングされた関数(通常「wrapper」と呼ばれる)が、元の関数をラップして追加機能を提供します。
デコレータの文法表現
デコレータを使用する際、@
記号の後にデコレータ名を記述し、それをデコレートされる関数の定義の直前に置きます。この記法は、デコレータ関数を呼び出して返された「ラップ」された関数を、元の関数に適用するシンタックスシュガーです。
たとえば、以下のようにデコレータを使用します:
@my_decorator
def my_function():
...
これは以下のようなコードの短縮形です:
def my_function():
...
my_function = my_decorator(my_function)
2. Flaskにおける@app.route
のようなデコレータの場合
特定のURLパスにアクセスがあったときに実行される関数(ビュー関数)を定義するために使用されます。これにより、ウェブアプリケーションのルーティングを簡単に設定できます。
以下の例では、@app.route('/hello')
デコレータはhello
関数を、アプリケーションのURLパス/hello
にマッピングします
@app.route('/hello')
def hello():
return 'Hello, World!'
デコレータは、元の関数(この例ではhello
)をそのままにしておきながら、追加的な機能(この例ではURLルーティング)は、Flaskの内部で定義されています。ユーザーはFlaskが提供するデコレータを使用するだけで、その内部で何が行われているかを深く理解する必要はありません。
3-1. 独自のデコレータを作成する場合
復習になりますが、デコレータを作成する場合、追加的な機能は、デコレータを定義する際にそのデコレータの中に記述します。Pythonのデコレータは基本的に関数です。この関数は別の関数を引数として取り、その関数に対して何らかの追加的な処理を行った後、新しい関数や元の関数を返します。
まず、独自のデコレータ my_decorator
と、それを使用する関数 my_function
を定義します。
def my_decorator(func):
def wrapper():
print("何かの前処理")
func()
print("何かの後処理")
return wrapper
@my_decorator
def my_function():
print("関数の本体")
次に、この my_function
関数を呼び出します。
my_function()
このコードを実行すると、以下の出力結果が得られます。
何かの前処理
関数の本体
何かの後処理
デコレータの動き
- デコレータ名(例:
my_decorator
)に対応する関数が呼び出され、引数として元の関数(例:my_function
)が渡されます。 - デコレータ関数内で定義されたネスティングされた関数(
wrapper
)が返されます。このwrapper
関数は、元の関数をラップし、追加機能を提供します。 - 最終的に、
wrapper
関数は元の関数の名前(my_function
)に割り当てられ、元の関数が呼ばれるときには代わりにwrapper
関数が実行されます。
このプロセスにより、デコレータは元の関数を変更せずに追加の機能を組み込むことが可能になります。
3-2. 引数なしの関数する呼び出し回数をカウントするデコレータの例
前の例より少し複雑な、独自のデコレータの例です。
def count_calls_decorator(func):
def wrapper():
wrapper.calls += 1
print(f"{func.__name__}は{wrapper.calls}回呼び出されました")
return func()
wrapper.calls = 0
return wrapper
@count_calls_decorator
def example_function():
print("関数が実行されました")
# 関数を何回か呼び出す
example_function()
example_function()
example_function()
count_calls_decorator
内の wrapper
関数は、デコレートされた関数example_function
を呼び出し、その呼び出し回数をカウントします。
このコードを実行すると、以下の出力が得られます。
関数が実行されました
example_functionは1回呼び出されました
関数が実行されました
example_functionは2回呼び出されました
関数が実行されました
example_functionは3回呼び出されました
ここで、各関数の呼び出しの後に、その関数がこれまでに何回呼び出されたかが表示されます。
wrapper.calls
の使用法で混乱したので、詳しく解説したいと思います。
関数属性としての wrapper.calls
wrapper.calls
という文法は、Python における関数属性の一例です。Python では、関数もオブジェクトであり、他のオブジェクトと同様に属性を持つことができます。wrapper.calls
は、wrapper
という関数に calls
というカスタム属性を追加し、それを利用して何か情報(この場合は呼び出し回数)を保持する方法です。
この概念を分解して説明します:
-
関数オブジェクトへの属性の割り当て: Python では、関数もオブジェクトであるため、属性を割り当てることができます。
wrapper.calls = 0
は、wrapper
関数にcalls
という新しい属性を作成し、その初期値を0
に設定しています。 -
属性の更新とアクセス: デコレーター内の
wrapper
関数が呼び出されるたびに、wrapper.calls
属性の値を 1 ずつ増やしています(wrapper.calls += 1
)。これにより、wrapper
関数がどれだけの回数呼び出されたかを追跡できます。 -
属性の利用:
wrapper.calls
の値は、関数が呼び出されるたびに出力され、関数がどれだけの回数実行されたかを示します。
この方法を使用することで、デコレーターを介して関数の呼び出し回数を追跡し、その情報を関数自体に結び付けることができます。これはPythonの柔軟な機能の一例で、関数を単なるコードの集まりではなく、状態を保持できるオブジェクトとして扱うことを可能にします。
関数に属性を追加する場合のポイント
-
属性の追加: 関数に属性を追加するには、関数名にドット(
.
)を続けて属性名を記述し、値を割り当てます。 - ネスティング: 属性は関数が定義されているのと同じスコープ(ネスティングレベル)で設定されます。
- 関数の独立性: この方法で設定された属性は、その関数に固有のものとなります。異なる関数には影響しません。