基礎から学ぶPython入門 90日コース | 実践Python - Day 26:デコレータ

90日で身につけるPython
スポンサーリンク
スポンサーリンク

Day 26:デコレータの「雰囲気」をつかむ

Day 26では、Pythonの少し不思議な機能である デコレータ・関数を引数に渡す・ラッパー関数 を扱います。 この段階では「仕組みのざっくりした理解」ができれば十分ですので、 難しく考えすぎず、「関数にちょっとしたオプションを後から足す仕組み」くらいの感覚で読んでいただければと思います。

関数を「値」として扱う感覚を持つ

関数も「変数に入る」し「引数として渡せる」

Pythonでは、関数も「値」として扱うことができます。 つまり、

  • 変数に代入できる
  • 別の関数の引数として渡せる
  • 別の関数から戻り値として返せる

ということです。

まずは、関数を変数に代入してみます。

def greet(name):
    """名前を受け取って挨拶する関数です。"""
    print(f"こんにちは、{name}さん。")


# 関数を変数に代入します。
hello = greet

# 変数経由で関数を呼び出します。
hello("Alice")  # greet("Alice") と同じ動きです。
Python

ここでのポイントは、

  • greet という「関数そのもの」を hello に代入していること
  • hello("Alice") と呼ぶと、greet("Alice") が実行されること

です。

次に、「関数を引数として渡す」例を見てみます。

def run_twice(func):
    """渡された関数を2回実行する関数です。"""
    func()
    func()


def say_hello():
    """挨拶を表示する関数です。"""
    print("Hello!")


# say_hello 関数を run_twice に渡します。
run_twice(say_hello)
Python

出力:

Hello!
Hello!

ここでのポイントは、

  • run_twice は「関数を受け取る関数」であること
  • func() と書くことで、「渡された関数」を実行していること

です。 この「関数を引数として渡す」感覚が、デコレータの理解につながっていきます。

ラッパー関数:関数の前後に「ひとこと」挟む

ラッパー関数とは何か

ラッパー関数(wrapper function) という言葉は、 「ある関数を呼び出す前後に、何かの処理を挟む関数」 という意味でよく使われます。

例えば、「関数の実行前後にログを出したい」という場合です。

def greet(name):
    """挨拶する関数です。"""
    print(f"こんにちは、{name}さん。")


def greet_with_log(name):
    """挨拶する前後にログを出すラッパー関数です。"""
    print("greet() を呼び出します...")
    greet(name)  # 元の関数を呼び出します。
    print("greet() の呼び出しが終わりました。")
Python

使ってみます。

greet_with_log("Alice")
Python

出力:

greet() を呼び出します...
こんにちは、Aliceさん。
greet() の呼び出しが終わりました。

ここでのポイントは、

  • greet_with_log が「greetを包んでいる(ラップしている)」こと
  • 元の関数の前後に「ログ出力」という追加の処理を挟んでいること

です。 この「ラッパー関数」の考え方が、そのままデコレータの仕組みにつながります。

デコレータとは何か:関数に「後から機能を足す」仕組み

デコレータのざっくりしたイメージ

デコレータ(decorator) は、

  • ある関数に対して
  • 「ラッパー関数」をかぶせることで
  • 「前後に何かの処理を追加する」仕組み

です。

イメージとしては、

  • 「関数に後からオプションを付ける」
  • 「関数の前後に共通処理を差し込む」

ようなものです。

デコレータの基本形:関数を受け取って関数を返す

デコレータは、「関数を受け取って、関数を返す関数」です。 まずは、ログを出すデコレータを作ってみます。

def log_decorator(func):
    """関数の前後にログを出すデコレータです。"""

    def wrapper(*args, **kwargs):
        """元の関数をラップするラッパー関数です。"""
        print(f"{func.__name__} を呼び出します...")
        result = func(*args, **kwargs)  # 元の関数を実行します。
        print(f"{func.__name__} の呼び出しが終わりました。")
        return result

    # wrapper 関数を返します。
    return wrapper
Python

ここでの重要ポイントは、

  • log_decorator は「関数 func を受け取る」関数であること
  • その中で wrapper というラッパー関数を定義していること
  • 最後に wrapper を返していること

です。

このデコレータを手動で使ってみます。

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


# greet を log_decorator でラップします。
greet = log_decorator(greet)

# ラップされた greet を呼び出します。
greet("Alice")
Python

出力:

greet を呼び出します...
こんにちは、Aliceさん。
greet の呼び出しが終わりました。

ここでのポイントは、

  • greet = log_decorator(greet) と書くことで、 「元の greet をラップした新しい関数」に置き換えていること
  • 呼び出し側は greet("Alice") と書くだけで、 「ログ付きの greet」が実行されるようになっていること

です。

デコレータ記法:@を使って「ラップする」ことを宣言する

@記法でデコレータを適用する

Pythonには、デコレータを簡単に適用するための @ 記法 が用意されています。 先ほどの例を、@log_decorator を使って書き直してみます。

def log_decorator(func):
    """関数の前後にログを出すデコレータです。"""

    def wrapper(*args, **kwargs):
        print(f"{func.__name__} を呼び出します...")
        result = func(*args, **kwargs)
        print(f"{func.__name__} の呼び出しが終わりました。")
        return result

    return wrapper


@log_decorator
def greet(name):
    """挨拶する関数です。"""
    print(f"こんにちは、{name}さん。")
Python

ここでの @log_decorator は、

greet = log_decorator(greet)
Python

という書き方と同じ意味です。

使ってみます。

greet("Alice")
Python

出力:

greet を呼び出します...
こんにちは、Aliceさん。
greet の呼び出しが終わりました。

ここでの重要ポイントは、

  • @log_decorator と書くことで、「この関数は log_decorator でラップされる」と宣言していること
  • 関数定義のすぐ上に書くので、「この関数にはどんなデコレータが付いているか」が一目で分かること

です。

デコレータの中身をもう少しだけ深掘りする

*args と **kwargs の役割

デコレータのラッパー関数では、よく *args**kwargs が使われます。

def wrapper(*args, **kwargs):
    result = func(*args, **kwargs)
    return result
Python

これは、

  • *args:位置引数(func(1, 2, 3) のような引数)をまとめて受け取る
  • **kwargs:キーワード引数(func(x=1, y=2) のような引数)をまとめて受け取る

ための書き方です。

こうしておくことで、

  • 引数の数や種類が違う関数にも、同じデコレータを使い回せる

ようになります。

複数の関数に同じデコレータを適用する

@log_decorator
def add(a, b):
    """足し算をする関数です。"""
    return a + b


@log_decorator
def multiply(a, b):
    """掛け算をする関数です。"""
    return a * b


print(add(2, 3))
print(multiply(4, 5))
Python

出力イメージ:

add を呼び出します...
add の呼び出しが終わりました。
5
multiply を呼び出します...
multiply の呼び出しが終わりました。
20

ここでのポイントは、

  • log_decorator を「共通の前後処理」として、複数の関数に適用できていること
  • 関数ごとに「ログの書き方」をコピペする必要がなくなっていること

です。

Day 26ミニテンプレート:デコレータ入門スクリプト

最後に、Day 26で学んだ内容をまとめて試せる「デコレータ入門スクリプト」のテンプレートを示します。

# day26_decorators.py
# デコレータ入門の練習用スクリプト

def log_decorator(func):
    """関数の前後にログを出すデコレータです。"""

    def wrapper(*args, **kwargs):
        # 関数名を表示します。
        print(f"[LOG] {func.__name__} を呼び出します...")
        # 元の関数を実行します。
        result = func(*args, **kwargs)
        # 実行後のログを表示します。
        print(f"[LOG] {func.__name__} の呼び出しが終わりました。")
        # 元の関数の戻り値を返します。
        return result

    return wrapper


@log_decorator
def greet(name):
    """挨拶する関数です。"""
    print(f"こんにちは、{name}さん。")


@log_decorator
def add(a, b):
    """足し算をする関数です。"""
    return a + b


def main():
    print("=== greet の呼び出し ===")
    greet("Alice")

    print("\n=== add の呼び出し ===")
    result = add(2, 3)
    print("add の結果:", result)


if __name__ == "__main__":
    main()
Python

Day 26のまとめ

Day 26では、デコレータ入門として、

  • 関数も「値」として扱え、変数に代入したり引数として渡せること
  • ラッパー関数で「元の関数の前後に処理を挟む」考え方
  • デコレータ=「関数を受け取って関数を返す関数」という仕組み
  • @デコレータ名 記法で、「この関数はデコレータでラップされる」と宣言する書き方
  • 共通処理(ログ出力など)を複数の関数に簡単に適用できるメリット

をステップバイステップで体験していただきました。

この段階では、「デコレータは関数の前後に共通処理を足すための仕組みなんだな」くらいの理解で十分です。 今後、ログ・認証・キャッシュ・計測など、さまざまな場面でデコレータが登場しますので、 今日の内容を「雰囲気の土台」として、少しずつ慣れていっていただければと思います。

タイトルとURLをコピーしました