Pythonにおけるデコレーター

Pythonにおけるデコレーター

この記事ではPythonにおけるデコレーターについて説明します。

YouTube Video

Pythonにおけるデコレーター

Pythonのデコレーターは、関数やメソッドに追加の機能を持たせるために使われる強力な機能です。デコレーターは、既存のコードを変更することなく、新しい機能を追加できるため、コードの再利用性や保守性を向上させます。

デコレーターの基本

Pythonのデコレーターは、関数を引数として受け取り、処理を追加した新しい関数を返す仕組みです。デコレーターを使用することで、関数に対して前処理や後処理などを簡単に追加できます。

デコレーターを使わない場合、関数の振る舞いを変更するには、その関数自体を直接編集する必要がありますが、デコレーターを使うことで、元の関数の実装を変更せずに機能を拡張できます。

基本的なデコレーターの構造

 1def my_decorator(func):
 2    def wrapper():
 3        print("Before the function call")
 4        func()
 5        print("After the function call")
 6    return wrapper
 7
 8@my_decorator
 9def say_hello():
10    print("Hello!")
11
12say_hello()
  • この例では、my_decorator関数はデコレーターとして機能し、say_hello関数をラップします。@my_decoratorという構文でデコレーターが適用され、say_hello()を呼び出すと、デコレーターが提供する前後の処理が自動的に実行されます。

デコレーターの仕組み

デコレーターは次のような手順で動作します。

  1. デコレーターは関数(またはメソッド)を引数として受け取ります。

  2. その関数を実行するためのラッパー関数(wrapper関数)を定義します。

  3. ラッパー関数は元の関数を実行する前や後に追加の処理を行います。

  4. デコレーターはラッパー関数を返します。これにより、元の関数がデコレーターでラップされた新しい関数として置き換えられます。

引数を受け取る関数に対するデコレーター

引数を受け取る関数に対してデコレーターを適用する場合、ラッパー関数もそれに応じて引数を受け取れるように変更する必要があります。

 1def my_decorator(func):
 2    def wrapper(*args, **kwargs):
 3        print("Before the function call")
 4        result = func(*args, **kwargs)
 5        print("After the function call")
 6        return result
 7    return wrapper
 8
 9@my_decorator
10def greet(name):
11    print(f"Hello, {name}!")
12
13greet("Alice")
  • *args**kwargsを使うことで、任意の引数とキーワード引数を受け取る関数にも対応できます。このようにすることで、あらゆる引数の関数に対して汎用的にデコレーターを適用できるようになります。

デコレーターの応用例

ログ出力のためのデコレーター

デコレーターは、ログ出力を追加する場合によく使用されます。例えば、関数の実行前後でログを出力するデコレーターを作成することで、関数がいつ呼び出されたかや、処理時間を記録できます。

 1import time
 2
 3def log_time(func):
 4    def wrapper(*args, **kwargs):
 5        start_time = time.time()
 6        print(f"Calling function '{func.__name__}'...")
 7        result = func(*args, **kwargs)
 8        end_time = time.time()
 9        print(f"Function '{func.__name__}' completed in {end_time - start_time} seconds")
10        return result
11    return wrapper
12
13@log_time
14def long_task(duration):
15    time.sleep(duration)
16    print("Task completed!")
17
18long_task(2)
  • この例では、log_timeデコレーターが関数の実行時間を計測し、ログとして出力します。long_task関数がデコレーターでラップされており、実行時に処理時間が記録されます。

権限管理のためのデコレーター

デコレーターは、権限管理にも利用できます。たとえば、ユーザーが特定の権限を持っているかどうかを確認して、処理を制限することができます。

 1def requires_permission(user_role):
 2    def decorator(func):
 3        def wrapper(*args, **kwargs):
 4            if user_role != "admin":
 5                print("Permission denied!")
 6                return
 7            return func(*args, **kwargs)
 8        return wrapper
 9    return decorator
10
11@requires_permission("admin")
12def delete_user(user_id):
13    print(f"User {user_id} has been deleted.")
14
15delete_user(123)  # Executed
16delete_user = requires_permission("guest")(delete_user)
17delete_user(456)  # Permission denied!
  • requires_permissionデコレーターは、ユーザーのロールに基づいて関数の実行を制限します。このようなロジックをデコレーターにまとめることで、権限管理がコードの各所に散らばることを防ぎ、可読性を高められます。

デコレーターのネスト

デコレーターは複数適用することも可能です。複数のデコレーターを一つの関数に適用する場合、上から順にデコレーターが実行されます。

 1def uppercase(func):
 2    def wrapper(*args, **kwargs):
 3        result = func(*args, **kwargs)
 4        return result.upper()
 5    return wrapper
 6
 7def exclaim(func):
 8    def wrapper(*args, **kwargs):
 9        result = func(*args, **kwargs)
10        return result + "!"
11    return wrapper
12
13@uppercase
14@exclaim
15def greet(name):
16    return f"Hello, {name}"
17
18print(greet("Alice"))  # "HELLO, ALICE!"
  • この例では、greet関数に対して2つのデコレーターが適用されています。@exclaimで文字列の末尾に感嘆符を追加し、次に@uppercaseで文字列を大文字に変換しています。

クラスメソッドに対するデコレーター

デコレーターはクラスメソッドにも使用できます。特に、クラス内でメソッドの振る舞いを制御したい場合に便利です。クラスメソッドにデコレーターを適用する際には、selfclsといった引数に注意が必要です。

 1def log_method_call(func):
 2    def wrapper(self, *args, **kwargs):
 3        print(f"Calling method '{func.__name__}'...")
 4        return func(self, *args, **kwargs)
 5    return wrapper
 6
 7class MyClass:
 8    @log_method_call
 9    def greet(self, name):
10        print(f"Hello, {name}")
11
12obj = MyClass()
13obj.greet("Bob")
  • この例では、greetメソッドにlog_method_callデコレーターを適用し、メソッドの呼び出し時にログを出力します。

デコレーターの注意点

デコレーターを使用する際には、いくつかの注意点があります。デコレーターは元の関数の名前やドキュメント文字列(__name____doc__)を変更してしまう可能性があるため、functools.wrapsを使用してこれらの情報を維持することが推奨されます。

 1import functools
 2
 3# Decorator without functools.wraps
 4def bad_decorator(func):
 5    def wrapper(*args, **kwargs):
 6        print("Before the function call")
 7        return func(*args, **kwargs)
 8    return wrapper
 9
10# Decorator with functools.wraps
11def good_decorator(func):
12    @functools.wraps(func)
13    def wrapper(*args, **kwargs):
14        print("Before the function call")
15        return func(*args, **kwargs)
16    return wrapper
17
18# Apply decorators
19@bad_decorator
20def my_function_bad():
21    """This is the original docstring for my_function_bad."""
22    print("Executing my_function_bad")
23
24@good_decorator
25def my_function_good():
26    """This is the original docstring for my_function_good."""
27    print("Executing my_function_good")
28
29# Run functions
30print("=== Without functools.wraps ===")
31my_function_bad()
32print("Function name:", my_function_bad.__name__)
33print("Docstring:", my_function_bad.__doc__)
34
35print("\n=== With functools.wraps ===")
36my_function_good()
37print("Function name:", my_function_good.__name__)
38print("Docstring:", my_function_good.__doc__)
  • @functools.wrapsを使用することで、元の関数のメタデータがラッパー関数に正しく引き継がれます。

まとめ

Pythonのデコレーターは、関数やメソッドに追加の機能を簡潔に付与できる非常に強力なツールです。コードの重複を減らし、保守性や再利用性を向上させるために、様々なシーンで利用できます。デコレーターの仕組みを理解することで、より効率的で柔軟なコードを書くことが可能になります。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video