Python 速習チュートリアル

Python のデコレータ

1. Python デコレータとは

デコレータ(Decorator)は、既存のコードを変更することなく、関数やクラスの動作を修正したり拡張したりすることを可能にする、Python の非常に強力かつ便利なツールです。

これは「メタプログラミング」の一種であり、プログラムの一部がコンパイル時や実行時に他の部分を修正しようとする設計パターンを指します。

2. 前提知識:関数はオブジェクトである

デコレータを理解するために、まず Python における関数の特性を知る必要があります。Python では、関数は「ファーストクラスオブジェクト(第一級オブジェクト)」として扱われます。

2.1 関数を引数として渡す

関数は、他の関数に引数として渡すことができます。

def shout(text):
    return text.upper()

def whisper(text):
    return text.lower()

def greet(func):
    # 関数を引数として受け取り、実行する
    greeting = func("こんにちは、デコレータの世界へ")
    print(greeting)

greet(shout)
greet(whisper)

2.2 関数を戻り値として返す

関数は、別の関数を戻り値として返すことができます。

def create_adder(x):
    def adder(y):
        return x + y
    return adder

add_15 = create_adder(15)
print(add_15(10)) # 結果は 25

3. デコレータの作成

デコレータは本質的に、別の関数を引数として受け取り、その動作を拡張した新しい関数を返す関数です。

3.1 デコレータの基本構造

以下は、関数の実行前後にメッセージを表示するシンプルなデコレータの例です。

def my_decorator(func):
    def wrapper():
        print("関数の実行前の処理です")
        func()
        print("関数の実行後の処理です")
    return wrapper

def say_hello():
    print("Hello!")

# デコレータを適用する
say_hello = my_decorator(say_hello)

say_hello()

4. @ 記法(シンタックスシュガー)

Python では、@ シンボルを使用することで、より簡単にデコレータを関数に適用できます。これは「シンタックスシュガー(書きやすい構文)」と呼ばれます。

4.1 @ を使ったデコレート

前述の例を @ を使って書き換えると、以下のようになります。

def my_decorator(func):
    def wrapper():
        print("準備中...")
        func()
        print("完了しました。")
    return wrapper

@my_decorator
def say_hi():
    print("Hi!")

say_hi()

5. 引数を持つ関数のデコレート

デコレートしたい元の関数が引数を受け取る場合、ラッパー関数(wrapper)でもそれらの引数を受け取れるように設計する必要があります。一般的には *args**kwargs を使用して、柔軟に対応させます。

5.1 引数への対応例

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def greet(name):
    print(f"こんにちは、{name}さん")

greet("ジョン")

6. 戻り値のある関数のデコレート

関数が値を返す場合、ラッパー関数内で元の関数を実行した際の戻り値をキャプチャし、それを明示的に返す必要があります。

6.1 戻り値のハンドリング例

def simple_decorator(func):
    def wrapper_func(*args, **kwargs):
        print("実行中...")
        result = func(*args, **kwargs) # 戻り値を保存
        return result # 戻り値を呼び出し元に返す
    return wrapper_func

@simple_decorator
def add_numbers(a, b):
    return a + b

value = add_numbers(10, 20)
print(f"計算結果: {value}")

7. 複数のデコレータの適用

一つの関数に対して、複数のデコレータを重ねて適用することも可能です。その場合、関数に近い方(下にある方)から順番に適用されます。

def bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def get_text():
    return "Hello World"

print(get_text()) # 出力: <b><i>Hello World</i></b>

このように、デコレータを活用することで、ロギング、アクセス制御、実行時間の計測などの共通処理を、ビジネスロジックから切り離してスマートに実装できるようになります。