Python デコレータ

デコレータとは?

デコレータは、関数やクラスを修飾(デコレート)するための構文です。デコレータを使うと、関数やクラスの動作を簡単に拡張したり修正したりできます。デコレータは他の関数を引数として受け取り、新しい関数を返します。

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

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        # ここで元の関数の前後に何か処理を追加
        print("Wrapper executed this before {}".format(original_function.__name__))
        result = original_function(*args, **kwargs)
        print("Wrapper executed this after {}".format(original_function.__name__))
        return result
    return wrapper_function

@decorator_function
def display():
    print("Display function ran")

# デコレータを使った関数の呼び出し
display()

この例では、@decorator_function を使って display 関数をデコレートしています。display 関数が呼ばれる前後にラッパー関数内のコードが実行されます。

関数デコレータ

関数デコレータは、関数を修飾するために使用されます。よく使われる関数デコレータの例として、ログ記録やアクセス制御、計測などがあります。

例1:ログ記録デコレータ

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Function {func.__name__} is called with arguments {args} and {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def add(x, y):
    return x + y

print(add(5, 3))  # Function add is called with arguments (5, 3) and {}
                  # 8

クラスデコレータ

クラスデコレータは、クラスを修飾するために使用されます。クラスデコレータは、クラス定義を変更したり、クラスに新しい機能を追加したりするのに役立ちます。

例2:クラスデコレータ

def class_decorator(cls):
    class Wrapped(cls):
        def __init__(self, *args, **kwargs):
            print(f"Creating instance of {cls.__name__}")
            super().__init__(*args, **kwargs)
    return Wrapped

@class_decorator
class MyClass:
    def __init__(self, value):
        self.value = value

# クラスデコレータを使ったクラスのインスタンス生成
instance = MyClass(10)

複数のデコレータ

関数やメソッドに複数のデコレータを適用することもできます。複数のデコレータは、内側から外側の順に適用されます。

例3:複数のデコレータ

def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("Decorator One")
        return func(*args, **kwargs)
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("Decorator Two")
        return func(*args, **kwargs)
    return wrapper

@decorator_one
@decorator_two
def greet():
    print("Hello")

# 複数のデコレータを使った関数の呼び出し
greet()
# 出力:
# Decorator One
# Decorator Two
# Hello

自作デコレータ

自作デコレータを作ることで、独自の処理を関数やクラスに追加することができます。以下に簡単なデコレータの自作例を示します。

例4:タイミング計測デコレータ

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timing_decorator
def compute(n):
    total = 0
    for i in range(n):
        total += i
    return total

print(compute(1000000))

functools モジュールの利用

functools モジュールには、デコレータを簡単に作成するためのユーティリティ関数が含まれています。特に、@functools.wraps デコレータは、ラッパー関数が元の関数のメタデータを保持するのに役立ちます。

例5:functools.wraps の利用

import functools

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling function {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def say_hello(name):
    """Greet someone by name."""
    print(f"Hello, {name}")

# デコレータを使った関数の呼び出し
say_hello("Alice")

# メタデータの確認
print(say_hello.__name__)  # say_hello
print(say_hello.__doc__)   # Greet someone by name.

@functools.wraps を使用すると、デコレートされた関数が元の関数の名前やドキュメントストリングなどのメタデータを保持することができます。


まとめ

関数デコレータ:関数の前後に追加処理を挟むために使用されます。
クラスデコレータ:クラス定義を修飾するために使用されます。
複数のデコレータ:関数やメソッドに複数のデコレータを適用できます。
自作デコレータ:独自の処理を追加するためのデコレータを作成できます。
functools モジュール:デコレータ作成を支援するユーティリティ関数が含まれています。

技術的な習得やデザインパターンの適用は、経験と習慣の積み重ねによる部分が大きいです。デコレータに限らず、設計やコードの最適化をリアルタイムで考えられるようになるには、次のようなステップが役立つかもしれません。

ステップ1:基本を理解する

まず、デコレータや他のデザインパターンの基本的な使い方をしっかり理解していることが前提です。これは既に学習されているようですので、次のステップに進みます。

ステップ2:小さなプロジェクトで練習する

デコレータを意識的に使うために、小さなプロジェクトやスクリプトで練習します。以下のような簡単なタスクを設定し、デコレータを使用して解決する練習を繰り返します。

練習例

1. ログ記録デコレータ:関数呼び出し時にログを記録するデコレータを作成し、いくつかの関数に適用します。
2. キャッシュデコレータ:計算結果をキャッシュするデコレータを作成し、再計算を防ぐようにします。
3. アクセス制御デコレータ:関数の実行前にアクセス権をチェックするデコレータを作成します。

ステップ3:パターンを見つける

次に、実際のプロジェクトでデコレータが有用なパターンを見つける練習をします。よくあるデコレータの使用パターンには以下のようなものがあります。

同じ前処理や後処理を複数の関数で行っている場合
エラーハンドリングを統一したい場合
関数の実行時間を測定したい場合


ステップ4:リファクタリングを意識する

リファクタリングはコードの品質を保つために重要なプロセスです。デコレータを使ったリファクタリングの具体的な例を挙げると、以下のようになります。

例:ログ記録のリファクタリング

リファクタリング前

def process_data(data):
    print("Processing data")
    # データ処理ロジック
    return data

def analyze_data(data):
    print("Analyzing data")
    # データ解析ロジック
    return data

def save_data(data):
    print("Saving data")
    # データ保存ロジック
    return data

リファクタリング後

import functools

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} is running")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def process_data(data):
    # データ処理ロジック
    return data

@log_decorator
def analyze_data(data):
    # データ解析ロジック
    return data

@log_decorator
def save_data(data):
    # データ保存ロジック
    return data

ステップ5:コードレビューとフィードバック

他の開発者からのフィードバックを得ることも非常に有効です。コードレビューを依頼し、デコレータの適用やリファクタリングの方法について意見をもらうことで、新たな視点を得ることができます。

ステップ6:設計段階でデコレータを考慮する

プロジェクトの設計段階で、デコレータを使用する場面を意識的に考える習慣をつけます。設計時にデコレータの適用を計画することで、後からのリファクタリングを減らすことができます。


結論

デコレータや他の設計パターンを自然に使いこなせるようになるためには、継続的な練習と経験が必要です。小さなプロジェクトでの練習、リファクタリングの実践、コードレビューの活用などを通じて、デコレータの使いどころを見つける力を養ってください。

この記事が気に入ったらサポートをしてみませんか?