関数の前についてる @ から始まるやつです。
正直、最初は「なんかこれつけると特殊な動きをしてくれるやつなんだろうな〜」くらいの認識だったんですが、こいつについてちゃんと調べてみて記事にまとめてみました。
■ デコレータとは?
まず「デコレータってなに?」という話から。
デコレータは 関数に追加の機能を後付けできる仕組み です。
@decorator_name と関数の前に書くだけで、中身を直接いじらずに前後処理を追加したり挙動を変えたりできます。
例として、こんなコードがあったとします ⬇️
@timer
def hello():
print("こんにちは")
これ、実は内部的にはこう書いてるのと同じです:
hello = timer(hello)
つまり、デコレータは「関数を受け取る関数」
受け取った関数を、別の関数で包みます。そして、元の関数名で呼び出すと、実際には包装した新しい関数が実行されるようになります。
■ 関数もオブジェクトであること
デコレータを理解する上でめちゃくちゃ大事なのがこれです。
Pythonでは関数もオブジェクトなので、こんなことができます:
- 関数を変数に入れる
- 関数を引数に渡す
- 関数から関数を返す
- 関数をリストや辞書に入れる
たとえば ⬇️
def greet():
print("Hello!")
f = greet # 関数を変数に代入
f() # Hello!
こんな動きができるからこそ、デコレータが「関数を受け取って関数を返す」という仕組みを実現できます。
■ 関数内関数
デコレータを書くときによく出てくるのが 関数内関数。
関数の中に別の関数を書くアレです。
def outer():
msg = "outer"
def inner():
print(msg) # 外側の変数が使える
return inner
ここでポイントなのは、
内側の関数は外側のスコープにアクセスできる(クロージャ)ということ。
デコレータは、この仕組みを使って
- 元の関数を呼ぶ前に処理を追加したり
- 後に処理を追加したり
- 実行回数をログしたり
- 時間を計測したり
みたいなことができます。
■ デコレータを自作する方法
では実際に自分で作ってみます。
まずは一番シンプルな「前後にプリントを挟むだけ」のデコレータ。
def my_decorator(func):
def wrapper():
print("前処理:実行前です")
func()
print("後処理:実行後です")
return wrapper
そして使うときは ⬇️
@my_decorator
def greeting():
print("こんにちは")
greeting()
結果:
前処理:実行前です
こんにちは
後処理:実行後です
「なんか特殊な動きをしてくれるやつ」が自分で作れるようになりました!
■ 引数ありの関数にも対応する
実際には関数に引数が存在する場合があるので、*args と **kwargs を使って受け止められるようにします。
*args と **kwargs について
*args と **kwargs は、関数が受け取る引数の数や種類が事前にわからない場合に使います。
-
*args:位置引数を タプル として受け取る(例:add(1, 2, 3)→args = (1, 2, 3)) -
**kwargs:キーワード引数を 辞書 として受け取る(例:func(name="太郎", age=20)→kwargs = {'name': '太郎', 'age': 20})
デコレータでこれを使うことで、どんな引数の関数にも対応できるラッパーが作れます。
つまり wrapper(*args, **kwargs) と書いておけば、元の関数がどんな引数を取っていても、そのまま渡せるようになります。
def debug(func):
def wrapper(*args, **kwargs):
print(f"{func.__name__} を実行します")
return func(*args, **kwargs)
return wrapper
@debug
def add(a, b):
return a + b
print(add(3, 5))
結果:
add を実行します
8
■ まとめ
- デコレータは 関数に後付けで機能を追加する仕組み
- Pythonでは 関数がオブジェクトなので関数を受け取って返せる
- デコレータは 関数内関数 を使って作られている
- デコレータを使えば、ログ出力・時間計測・権限チェックなど、様々な共通処理を簡単に追加できる
■ おわりに
デコレータに対して、最初は「なんか特殊なやつ」くらいのイメージでしたが、ちゃんと理解してみると「ああ、こういう仕組みだったのか!」ってなりましたね。
要するに、関数を受け取って、ちょっと処理を追加した関数を返してるだけでした。
Pythonでは関数もオブジェクトとして扱えるから、こういう柔軟な書き方ができるわけですね。
実際に使ってみると、ログ出力とか実行時間の計測とか、毎回書くのが面倒な処理を@一つでサクッと追加できるので超便利です。