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()
PythonDay 26のまとめ
Day 26では、デコレータ入門として、
- 関数も「値」として扱え、変数に代入したり引数として渡せること
- ラッパー関数で「元の関数の前後に処理を挟む」考え方
- デコレータ=「関数を受け取って関数を返す関数」という仕組み
@デコレータ名記法で、「この関数はデコレータでラップされる」と宣言する書き方- 共通処理(ログ出力など)を複数の関数に簡単に適用できるメリット
をステップバイステップで体験していただきました。
この段階では、「デコレータは関数の前後に共通処理を足すための仕組みなんだな」くらいの理解で十分です。 今後、ログ・認証・キャッシュ・計測など、さまざまな場面でデコレータが登場しますので、 今日の内容を「雰囲気の土台」として、少しずつ慣れていっていただければと思います。
