🍣

【Python】デコレータ(decorator)とは何か?

に公開

今日は、Pythonのとっても便利な機能「デコレータ」について、わかりやすくご紹介します。

デコレータって聞くと難しそうに感じますが、実はケーキにトッピングをするみたいなもので、とってもシンプルな考え方なんです!

デコレータって何?

デコレータとは、簡単に言うと「関数を包み込んで、新しい機能を追加するための仕組み」。

名前の通り、何かを「装飾する(デコレーションする)」ためのものなんです。

例えば、お誕生日ケーキを想像してみてください。

普通のケーキにイチゴやチョコレートをトッピングすると、見た目も味も変わりますよね?

それと同じで、デコレータは既存の関数に新しい機能を「トッピング」するんです。

デコレータの基本的な使い方

Pythonでデコレータを使うときは、「@」記号を使います。これは「アットマーク」と読みます。

def my_decorator(func):
    def wrapper():
        print("デコレータで追加した処理: 関数実行前")  # デコレータで追加した処理
        func()  # 元の関数を実行
        print("デコレータで追加した処理: 関数実行後")  # デコレータで追加した処理
    return wrapper

@my_decorator
def say_hello():
    print("こんにちは!")

# 関数を実行
say_hello()

# 出力:
# デコレータで追加した処理: 関数実行前
# こんにちは!
# デコレータで追加した処理: 関数実行後

このコードでは、say_hello関数の前後に、新しい処理(メッセージを表示する)を追加しています。@my_decoratorという記号で、say_hello関数をデコレーター(my_decorator)で包んでいるんです。

デコレータのしくみをもっと詳しく

デコレータの働きをもうちょっと詳しく見てみましょう。

実は、@my_decoratorを使うことは、以下のコードと同じ意味になります。

def say_hello():
    print("こんにちは!")

# デコレータを使わない場合は、こう書きます
decorated_hello = my_decorator(say_hello)
decorated_hello()


つまり、デコレータは関数を別の関数で包んで、新しい関数を作り出しているんです。

おしゃれな包装紙でプレゼントを包むようなイメージですね!

デコレータで引数を扱う方法

実際のプログラムでは、関数に引数がある場合が多いですよね。

デコレータでそんな関数を扱うには、以下のようにします。

def my_decorator_with_args(func):
    def wrapper(*args, **kwargs):
        print(f"関数が実行されました。引数: {args}, キーワード引数: {kwargs}")  # 引数の表示
        result = func(*args, **kwargs)  # 元の関数を実行して結果を取得
        print(f"関数の実行が終わりました。結果: {result}")  # 結果の表示
        return result  # 結果を返す
    return wrapper

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

# 関数を実行
result = add_numbers(3, 5)
print(f"最終結果: {result}")

# 出力:
# 関数が実行されました。引数: (3, 5), キーワード引数: {}
# 関数の実行が終わりました。結果: 8
# 最終結果: 8

このコードでは、*args**kwargsという特殊な記法を使って、どんな引数でも受け取れるようにしています。

まるで何でも入れられる魔法のポケットのようですね!

ここでは**kwargsは必須ではありません。

**kwargsは、キーワード引数(名前付き引数)を任意の数だけ受け取るための構文です。

例の中では実際にキーワード引数を使っていないため、省略することもできます。

ただし、実際のプログラミングでは、将来的にキーワード引数が必要になる可能性も考慮して、*args**kwargsを両方含めておくことが多いです。

これは「念のため」というか、より汎用的なデコレータにするための習慣のようなもの。

デコレータを書くときは、ラップする関数がどんな引数を取るか分からない場合が多いので、*args, **kwargsの両方を含めておくと安全です。

でも、確実にキーワード引数を使わないとわかっている場合は、**kwargsを省略しても問題ありません!

デコレータの実用例

デコレータは実際のプログラミングでどう使われるのでしょうか?いくつか例を見てみましょう。

例1: 実行時間を計測するデコレータ

import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()  # 開始時間を記録
        result = func(*args, **kwargs)  # 関数を実行
        end_time = time.time()  # 終了時間を記録
        print(f"関数 {func.__name__} の実行時間: {end_time - start_time:.4f}秒")  # 実行時間を表示
        return result
    return wrapper

@measure_time
def slow_function():
    time.sleep(1)  # 1秒間停止
    print("処理が完了しました")

# 関数を実行
slow_function()

# 出力:
# 処理が完了しました
# 関数 slow_function の実行時間: 1.0013秒

このデコレータは、関数の実行にかかる時間を測定して表示します。

特に時間がかかる処理のパフォーマンスを確認するのに便利です。

例2: ログを記録するデコレータ

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"関数 {func.__name__} が呼び出されました")  # 関数の呼び出しを記録
        result = func(*args, **kwargs)  # 関数を実行
        print(f"関数 {func.__name__} が終了しました")  # 関数の終了を記録
        return result
    return wrapper

@log_function_call
def greet(name):
    print(f"こんにちは、{name}さん!")
    return f"{name}さんへの挨拶が完了しました"

# 関数を実行
result = greet("太郎")
print(result)

# 出力:
# 関数 greet が呼び出されました
# こんにちは、太郎さん!
# 関数 greet が終了しました
# 太郎さんへの挨拶が完了しました

このデコレータは、関数の呼び出しと終了のタイミングをログとして記録します。

デバッグやプログラムの流れを追跡するのに役立ちます。

デコレータのまとめ

デコレータの特徴を表でまとめてみましょう。

特徴 説明
構文 @デコレータ名 を関数の前に記述
主な用途 既存の関数に新しい機能を追加する
メリット コードの再利用性が高まる、機能の追加が簡単
実用例 実行時間の計測、ログの記録、認証、キャッシュなど

まとめ

Pythonのデコレータは、既存の関数に新しい機能を追加するための強力な仕組みです。

ケーキにトッピングを追加するように、簡単に機能を拡張することが可能。

デコレータを使いこなせるようになると、コードがよりスッキリして、読みやすくなります。

また、同じコードを繰り返し書く必要がなくなるので、プログラマーの大敵である「繰り返し(DRY原則:Don't Repeat Yourself)」も避けられます。

ぜひ、自分のプログラムでデコレータを試してみてください。

Discussion