Pythonのコードを読んでいて、関数の上に @something という記号がついているのを見て「これ何だろう?」と思ったことはありませんか?これがデコレーターと呼ばれる仕組みで、関数の中身を一切書き換えずに、その前後に処理を追加したり挙動をカスタマイズしたりできる、Pythonならではの強力な機能です✨
デコレーターを使いこなせるようになれば、ログ出力、認証チェック、実行時間計測、キャッシュ処理など、「複数の関数に共通する処理」を1行で適用できるようになります。コードがDRY(Don't Repeat Yourself)になり、レビューでも一目置かれる存在になれますよ🚀
デコレーターとは、ある関数に対して、その関数のコードを変更せずに処理を追加・変更する仕組みのことです。「decorate(装飾する)」する人=デコレーター、というわけですね🎨
例えば「これはりんごです」と表示するだけのシンプルな関数があったとします。
def print_apple():
print("これはりんごです")
この関数に @exec_time のようなデコレーターをつけるだけで、関数本体を書き換えることなく「実行直前の日時」と「実行完了後の日時」を自動で表示するように改造できます。同じ関数を呼び出しても、前後に新しい処理が挟まる——これがデコレーターのイメージです。
これらを各関数に書き加えるのではなく、デコレーターとして外側から付与することで、関数の本体は本来のロジックだけに集中させられます。
デコレーターを自作する前に、押さえておくべき3つのポイントがあります。順番に見ていきましょう。
Pythonでは、関数も他の値と同じくオブジェクトとして扱えます。つまり、変数に代入したり、引数に渡したり、戻り値として返したりできるのです。
def print_apple():
print("これはりんごです")func = print_apple # 括弧をつけずに代入(呼び出しではない)
func() # → "これはりんごです" と表示される
print_apple に () をつけずに func に代入することで、関数自体を変数に格納できます。これを呼び出せば、元の関数と同じように動作します。
関数オブジェクトは普通の変数と同様に扱えるので、関数の引数にも、戻り値にも使えます。
def repeat_func_twice(func):
func()
func()repeat_func_twice(print_apple) # → "これはりんごです" が2回表示
このように関数を引数として渡せるからこそ、デコレーターは「対象の関数を受け取って、改造した関数を返す」という芸当ができるわけです。
Pythonでは関数の中にさらに関数を定義できます。これをネスト関数(内部関数)と呼びます。
def print_text_twice(text):
def add_exclamation(t):
return t + "!"
result = add_exclamation(text)
print(result)
print(result)
内部関数 add_exclamation は外からは呼び出せず、print_text_twice の中だけで使えます。ここで重要なのがスコープの概念です。Pythonは変数を探すとき、まず一番近い場所(関数の中)を探し、見つからなければ一つ外側、さらに外側……と順に探していきます。だから内部関数は、外側の関数の引数(例:text)にもアクセスできるのです。この性質がデコレーター実装の鍵になります🔑
準備が整ったので、実際にデコレーターを作ってみましょう。今回は「関数の実行前に start、実行後に end を表示する」シンプルなデコレーターを作ります。
def start_end(func):
def wrapper():
print("start")
func()
print("end")
return wrapperdef print_apple():
print("これはりんごです")decorated = start_end(print_apple)
decorated()
start_end は引数も戻り値も「関数」になっている関数です。受け取った関数の前後に start/end の表示を挟んだ新しい関数(wrapper)を返しています。実行すれば、start → これはりんごです → end の順に表示されます。
毎回 start_end(print_apple) と書くのは少し冗長ですよね。Pythonには@マーク(アットマーク)を使った糖衣構文があり、同じことが一目でわかる形に書き換えられます。
@start_end
def print_apple():
print("これはりんごです")print_apple() # → start / これはりんごです / end
これだけで先ほどと完全に同じ動作になります。「この関数には start_end を装飾するよ」という意図がコード上で一目瞭然になり、関数本体のロジックを一切汚さずに機能を追加できる、というのが@記法の魅力です🎀
実用的な関数は引数や戻り値を持つことがほとんど。先ほどのデコレーターを「どんな引数・戻り値の関数にも対応できる」形に進化させましょう。
def start_end(func):
def wrapper(*args, **kwargs):
print("start")
result = func(*args, **kwargs)
print("end")
return result
return wrapper@start_end
def print_text(text):
return text + "!"x = print_text("これはりんごです")
print(x)
ポイントは3つです。*args, **kwargs でどんな引数も受け取れるようにし、func(*args, **kwargs) でそのまま元の関数に渡し、戻り値を return することで呼び出し元に届けます。これで引数の数や戻り値の有無に関わらず、どんな関数にもデコレーターを適用できる汎用版の完成です🎉
デコレーターはクロージャ・スコープ・関数オブジェクトといったPythonの根幹概念が絡む奥深いテーマ。体系的に学べる書籍が手元にあると、理解の深さが段違いです✨
デコレーターやクロージャの正しい書き方、functools.wraps の使いどころなど、現場で役立つベストプラクティスが90項目に凝縮されています。
関数オブジェクト・スコープ・クロージャといったデコレーターの土台となる概念を、徹底的に解説。中級〜上級者を目指す方には必読の1冊です。
Pythonの基本構文を体系的に網羅。デコレーターの基礎やネスト関数の挙動など、迷ったときに頼れる定番書です。
長時間のコーディングでも疲れにくいキーボードがあると、写経学習の継続力が大きく変わります。
コードと公式ドキュメントを並べて見られるデュアルディスプレイは、デコレーターのような複雑なテーマを学ぶ際の強力な味方になります。
A. はい、できます。@deco1 と @deco2 を関数の上に並べて書くと、下に書いたものから順に適用されます。例えば @deco1 / @deco2 / def func() の順なら、deco1(deco2(func)) と等価になります。
functools.wraps って何?A. デコレーターを適用すると、元の関数の名前(__name__)やドキュメント文字列(__doc__)が wrapper のものに上書きされてしまいます。functools.wraps をネスト関数の上にデコレーターとして付けると、これらのメタ情報を元の関数のまま保持できます。実務では必ずセットで使うのが定番です。
A. 作れます。@my_deco("hello") のように引数を渡すデコレーターは、「デコレーターを返す関数」として実装します。「3階建て関数」になるため少し複雑ですが、ロガーやリトライ処理などでよく使われる強力なパターンです。
A. はい、メソッドにも同じように使えます。Python標準の @staticmethod、@classmethod、@property などは、まさにメソッド向けのデコレーターです。自作のデコレーターも、メソッドの引数 self は *args の最初の要素として自然に受け取れます。
A. 関数呼び出しが1段階増えるため、厳密にはわずかなオーバーヘッドはあります。ただし通常のアプリケーションでは無視できるレベルで、コードの見通しの良さや保守性のメリットの方がはるかに大きいです。
デコレーターの本質は、「関数を引数に取り、改造した関数を返す関数」です。関数オブジェクト・ネスト関数・スコープ・可変長引数という4つのパーツが揃えば、自分でも自由に作れるようになります🎀
一度作ったデコレーターは何個の関数にでも使い回せるので、コードの重複が劇的に減ります。ログ出力・認証チェック・実行時間計測など、「複数の関数に共通する処理」を見つけたら、それは絶好のデコレーター化のチャンスです。今日学んだ書き方をぜひエディタで試して、Pythonコードをワンランク上のレベルへ進化させてみてください🚀