デコレータとは
デコレータとは、ある関数に別の処理を追加するための仕組みです。関数の中身を直接書き換えずに、外側から処理をくっつけることができます。
言葉だけではイメージしにくいと思いますが、役割・書き方・考え方の順番で整理していくと、その便利さが少しずつ見えてくるはずです。
まずは、「どんな問題を解決するための仕組みなのか」という部分から理解していきましょう。
【関連】Pythonのクラスとは?基本から継承まで初心者向けに徹底解説
【関連】Pythonの演算子とは?基本から応用まで初心者向けに解説
【関連】Pythonの変数埋め込みとは?使い方や注意点を初心者向けに解説
デコレータの基本的な役割
デコレータとは、関数の中身を直接書き換えずに、外側から処理を追加できる仕組みです。
例えば、5つの関数それぞれの先頭に「処理を開始します」という一行を書いていたとします。仕様が変わってそのメッセージを変えなければならなくなったとき、5か所すべてを修正しなければいけません。修正漏れが起きるリスクもあります。
デコレータを使うと、この「共通して追加したい処理」を一か所にまとめて、各関数に適用できるのです。修正が必要になっても、デコレータの中身を1か所直すだけで全体に反映されます。
関数の中身には手を加えず、外側からくっつけるイメージです。最初は「便利な追加処理の仕組み」という理解で十分です。
次のコードは、デコレータを使って関数の実行前にメッセージを表示する例です。
出力結果
ネコが走り出した
こんにちは@を使った書き方の意味
先ほどのコードでは、greet = my_decorator(greet)という書き方でデコレータを適用しました。しかし、これを毎回書くのは少し手間です。
そこで使えるのが@を使った書き方です。@my_decoratorのように書くだけで、直下の関数に自動でデコレータが適用されます。
前のセクションでgreet = my_decorator(greet)と書いていた処理を、@my_decoratorの一行で置き換えられるのです。
やっていることは同じですが、見た目がすっきりして「この関数にはこの処理が付いている」とひと目でわかるようになります。
次のコードは、前のセクションの例を@記法で書き直したものです。動作はまったく同じです。
出力結果
イヌが吠えた
こんにちは関数を引数として扱う考え方
デコレータを理解するには、「関数そのものを別の関数に渡せる」という考え方を押さえておく必要があります。
数字や文字列を変数に入れて別の場所に渡せるのと同じように、関数も変数に代入したり、別の関数の引数として渡したりできます。
デコレータは、この仕組みを使って「元の関数を受け取り、処理を加えた新しい関数を返す」という動きをしているのです。
関数を引数として渡すシンプルな例で確認してみましょう。
出力結果
トリが飛んだ
やあrun(say_hello)の部分に注目してください。say_helloに()がついていないのは、「関数を実行する」のではなく「関数そのものを渡す」ためです。この違いはデコレータでも重要なポイントになるので、覚えておいてください。
また、このコードがどのようなプロセスで動作しているか、処理の流れを追ってみましょう。
- run(say_hello)が最初に実行される
- このときsay_helloには()がついていないため、「関数そのもの」をrunに渡している
- runの中に入り、funcの中身がsay_helloになる
- print("トリが飛んだ")が実行され、画面に表示される
- func()が実行され、ここで初めてsay_helloが呼び出され「やあ」と表示される
defで始まる関数の定義部分は「こういう関数を用意するよ」という準備です。
プログラムはそこを一旦スルーして、実際に呼び出された行から動き始めます。このコード例でいうと、run(say_hello)に来て初めて処理がスタートする点を押さえておきましょう。
初心者がつまずきやすいポイント
デコレータを学ぶときに多くの初心者が戸惑う点は主に2つあります。
1つ目は「関数の中にさらに関数を書く」という書き方です。実際、デコレータのコードを初めて見たとき、多くの人は「なぜ関数の中にまた関数があるの?」と感じます。
これは「ネスト」と呼ばれる書き方で、デコレータでは内側の関数が「実際に実行したい処理のまとまり」になっています。内側の関数は外からは直接呼び出せず、デコレータを通して使われます。
2つ目は「関数を戻り値として返す」という特徴を持っている点です。外側の関数が内側の関数を返す(return _wrapper のような書き方)ことで、デコレータを適用した後も関数として呼び出せる状態を保っています。
関数を「実行」するのではなく「渡す」という点が最初はわかりにくいですが、()をつけずに書くのが「関数そのもの」を渡すサインだと覚えておきましょう。
出力結果
(この定義だけでは出力なし。関数に適用してから呼び出すことで動作します)
このコード例でreturn _wrapperに()がついていないのは、「関数そのものを渡すサイン」です。
ここを意識しておくだけでも、デコレータのコードは非常に読みやすくなります。
デコレータの基本的な書き方
デコレータの役割を理解したところで、次は実際のコードの書き方を確認します。
最小限の構成から始めて、引数や戻り値の扱いまで順を追って見ていきます。自分でも実際に手を動かしながら読むと、より理解が定着するはずです。
ぜひ、一緒に書きながら進めていきましょう。
最小構成のデコレータ
デコレータの基本的な形は、次の3つで成り立っています。
- 外側の関数(元の関数を引数として受け取る)
- 内側の関数(追加したい処理と元の関数の呼び出しをまとめる)
- 外側の関数が内側の関数を返す(実行はしない)
この3つの構造がわかると、どんなデコレータのコードを見ても読み解きやすくなるでしょう。
まずは、シンプルな例で確認してみます。
出力結果
クマが歩き始めた
出発しますこのコードの処理の流れを追う前に、@simple_decorator という書き方が裏側で何をしているかを先に再確認しておきましょう。
say = simple_decorator(say)つまり「simple_decoratorにsayを渡して、返ってきたものをsayという名前で上書きする」という代入が、@の裏側で自動的に起きています。
これを踏まえた上で、処理の流れを見てみましょう。
- Pythonがコードを読み込んだ時点で@simple_decoratorが実行され、sayは_wrapper に置き換わる(simple_decorator(say) を実行すると、return _wrapperで_wrapper が返ってくるため)
- say()を呼び出し、実際には_wrapper()が動く
- _wrapper() の中で print("クマが歩き始めた") が実行される
- func()が呼び出され、元のsay()の中身が実行されて「出発します」と表示される
値が頻繁に切り替わるのが、難しい部分だと思います。
@という記号に「渡して、返ってきたものを上書きする」という意味が含まれている点が、最初の壁です。ここさえ押さえれば、デコレータの流れは追いやすくなります。
また、funcは「simple_decorator に渡された関数をそのまま受け取っている変数」くらいに考えておくと、func()でsay()が実行されるイメージがつくかもしれません。
最初は、理解するのがかなり難しいと思います。しかし、その分デコレータを理解できればプログラミングの力は確実に上がります。
この段階で、すべてを理解できなくても大丈夫です。1つずつ理解していきましょう。
元の関数の前後に処理を追加する方法
デコレータがよく使われる場面として、関数の実行前後に何か処理を挟みたい場合が挙げられます。ログを残したり、処理の開始と終了を表示したりしたいときに有用です。
やり方はシンプルで、内側の関数の中でfunc()の前後に処理を書くだけです。func()の前に書いたものは関数が始まる前に、後ろに書いたものは関数が終わった後に実行されます。
実際に、関数の前後にメッセージを表示するデコレータを見てみましょう。
出力結果
キツネが森に入った
探索中...
キツネが森から出た処理の流れを確認しておきましょう。
- explore()が呼び出される。@before_afterが適用されているため、実際には_wrapper()が動く
- func()の前にあるprint("キツネが森に入った")が実行される
- func()が呼び出され、元のexplore()の中身が実行されて「探索中...」と表示される
- func()の後にあるprint("キツネが森から出た")が実行される
func()の前後に処理を書くだけで、元の関数には手を加えずに前処理・後処理を追加できます。
引数を受け取る関数への対応
デコレータを適用した関数が引数を受け取る場合、内側の関数にも同じ引数を受け取る仕組みが必要です。
ただ、デコレータを作るたびに「この関数の引数は何個か」を気にするのは大変です。そこで使えるのが*argsと**kwargsです。この2つを使うと、引数の数や種類を問わずどんな関数にも対応できる汎用的なデコレータが作れます。
ちなみに、*argsは位置引数(順番で渡す引数)をまとめて受け取り、**kwargsはキーワード引数(名前を指定して渡す引数)をまとめて受け取る役割です。
出力結果
パンダが動き始めた
こんにちは、田中さん処理の流れを確認します。
- greet("田中")が呼び出される。@log_callが適用されているため、実際には_wrapper("田中")が動く
- _wrapperは*argsで引数を受け取るため、"田中"がargsに格納される
- print("パンダが動き始めた")が実行される
- func(*args, **kwargs)が呼び出され、受け取った引数がそのまま元のgreet()に渡されて「こんにちは、田中さん」と表示される
*args と **kwargs を使うことで、元の関数がどんな引数を受け取る場合でも対応できます。デコレータを汎用的に作るときの定番の書き方として覚えておきましょう。
戻り値を扱うときの注意点
元の関数がreturnで値を返す場合、内側の関数でもその値をreturnで返す必要があります。
この一手間を忘れると、デコレータを適用した後に戻り値が失われるためNoneになってしまいます。計算結果や処理の状態など、値を受け取って使いたい場面では特に注意しましょう。
result = func(...)のように一度変数に受けてからreturn resultで返す書き方にしておくと、後から値を確認したり加工したりしやすくなるのでおすすめです。
次のコードでは、戻り値を正しく返すデコレータの例を示します。
出力結果
シカが走り出した
7処理の流れを確認しておきましょう。
- print(add(3, 4))が実行される。@log_callが適用されているため、実際には _wrapper(3, 4) が動く
- print("シカが走り出した")が実行される
- result = func(3, 4)が呼び出され、元のadd()が実行されて3 + 4 = 7がresultに入る
- return resultで7が返され、外側のprint()によって画面に表示される
result = func(...)で一度変数に受けてから返しているのがポイントです。こうしておくと、後から戻り値に何か処理を加えたいときにも対応しやすくなります。
デコレータの使いどころ
デコレータの書き方を覚えたら、次は「どんな場面で使うのか」を知っておきましょう。
よく使われるパターンや使いどころを知っておくと、デコレータを使うべき場面と使わなくてよい場面の判断がしやすくなります。
共通処理のまとめ方
いくつもの関数に同じ処理を書き続けていると、後から修正が必要になったときにすべての箇所を直す手間がかかります。
修正する箇所が多いと、書き忘れや修正漏れも起きがちです。
こんなときデコレータを使えば、共通の処理を1つの場所にまとめて各関数に適用できます。修正が必要になっても、デコレータの中身を直すだけで全体に反映されます。
共通の案内メッセージを複数の関数に適用する例を見てみましょう。
出力結果
ライオンが準備した
タスクAを実行
ライオンが準備した
タスクBを実行処理の流れを確認しましょう。
- task_a()が呼び出される。@announceが適用されているため、実際には_wrapper()が動く
- print("ライオンが準備した")が実行される
- return func(*args, **kwargs)で元のtask_a()が呼び出され、「タスクAを実行」と表示される
- 続けてtask_b()が呼び出され、同じ流れで「ライオンが準備した」→「タスクBを実行」と表示される
2つの関数に同じ@announceを付けるだけで、共通のメッセージが自動的に挿入されます。関数が増えても、デコレータを付けるだけで対応できるのがポイントです。
ログ出力や実行時間の計測
関数が呼ばれた事実を記録する「ログ出力」や、処理にどれくらい時間がかかったかを測る「実行時間の計測」は、デコレータが使われやすい代表的な場面です。
なぜなら、その処理を関数の本体に直接書くと、本来やりたい処理と記録のための処理が混在してコードが読みにくくなるためです。
こういった処理は関数の本来の目的とは関係がないため、デコレータとして切り出すと本体のコードがすっきりします。
ちなみに、timeモジュールを組み合わせると、処理時間の計測も手軽に実装できます。
出力結果
処理完了
オオカミの行動時間: 0.5001秒処理は次のような流れで動いています。
- heavy_task()が呼び出される。@measure_timeが適用されているため、実際には _wrapper()が動く
- start = time.time()で処理開始時刻を記録する
- result = func(*args, **kwargs)で元のheavy_task()が呼び出される。time.sleep(0.5)で0.5秒待機し、「処理完了」と表示される
- end = time.time()で処理終了時刻を記録し、end - startで経過時間を計算して表示する
time.time()で時刻を取得し、処理の前後で差を取るだけで経過時間が測れます。
heavy_task()本体には計測のコードが一切ないのに、実行時間がきちんと表示されるのがデコレータの便利なところです。
入力チェックや権限確認
Webアプリを作るとき、「ログインしているユーザーだけが見られるページ」を作りたい場合があります。そのチェック処理を各関数に毎回書くのは手間ですし、書き忘れるとセキュリティ上の問題になりかねません。
デコレータを使うと、「処理の入り口で条件を確認し、問題があればそこで止める」という流れを一か所にまとめて、必要な関数に適用できます。
次のコードでは、ログイン状態の確認を模したデコレータを見てみましょう。
出力結果
タカが立入禁止エリアに近づいた
ダッシュボードを表示します処理の流れは、次のようになっています。
- show_dashboard(False)が呼び出される。@require_loginが適用されているため、実際には _wrapper(False)が動く
- if not is_logged_in:の条件が真(ログインしていない)のため、「タカが立入禁止エリアに近づいた」と表示してreturnで処理を止める
- 次に show_dashboard(True)が呼び出される。今度はis_logged_inがTrueなので条件に引っかからない
- return func(is_logged_in, *args, **kwargs)で元のshow_dashboard()が呼び出され、「ダッシュボードを表示します」と表示される
ログインしていない場合はreturnで処理を止めているため、本来の関数は実行されません。このように「条件を満たさなければ先に進めない」という入り口の制御をデコレータで一元管理できます。
使わないほうがよいケース
デコレータは便利ですが、どんな場面でも使うべきではありません。
1つの関数にしか使わない処理や、処理の流れを順番に追いたいコードでは、デコレータを使うと「この関数を呼んだときに何が起きるのか」がわかりにくくなることがあります。
特に、小さなスクリプトや、一度しか使わない関数に無理にデコレータを当てはめると、かえってコードが複雑に見えてしまいます。
「同じ処理を複数の関数で繰り返し書いている」という状況でなければ、直接書く方がシンプルでわかりやすいでしょう。
使うと効果的な場面 |
使わないほうがよい場面 |
|---|---|
共通処理を複数の関数に適用する |
一度しか使わない処理 |
ログ・計測など本体と無関係な処理 |
学習中の小さなスクリプト |
権限チェックなどの入り口処理 |
処理の流れを明確に示したいとき |
複数のデコレータとクラスでの活用
デコレータは1つの関数にいくつもまとめて使ったり、クラスと組み合わせたりすることもできます。
Pythonにはクラス専用の組み込みデコレータもいくつか用意されており、覚えておくと役立つ場面もあるはずです。
ここでは、代表的なものを順番に確認していきましょう。
複数のデコレータを重ねる書き方
1つの関数に複数のデコレータを重ねて使うこともできます。重ねて書くときは、「下のデコレータから先に関数を包んでいく」というルールがあります。
別の言い方をすると、デコレータは下から順に適用されるということです。
@firstが上、@secondが下にある場合、まずsecondがperformを包み、次にfirstがその外側を包みます。
実行されるときは外側のfirstから始まり、second、元の関数へと進みます。
次のコードは、2つのデコレータを重ねている例です。
出力結果
クジャクが羽を広げた
クジャクが歩き始めた
パフォーマンス開始処理の流れを確認しておきましょう。
- @firstと@secondが重なっているため、まずperformに@secondが適用され、次に@firstが適用される
- perform()が呼び出されると、外側のfirstの_wrapper()が動く
- print("クジャクが羽を広げた")が実行される
- func()で内側のsecondの_wrapper()が呼び出され、print("クジャクが歩き始めた")が実行される
- さらにfunc()で元のperform()が呼び出され、「パフォーマンス開始」と表示される
@を上から読んだ順番とは逆に、下のデコレータから先に適用されます。
「包む順番」と「実行される順番」が直感と逆になりやすいので、迷ったときはこの流れを確認してみてください。
クラスで使われるデコレータ一覧
Pythonには、クラスの中で使える組み込みのデコレータがいくつかあります。自分で定義しなくてもそのまま使えるものばかりです。
代表的なデコレータの種類と役割を把握しておきましょう。
デコレータ |
役割の概要 |
|---|---|
@classmethod |
クラス自体を第一引数として受け取るメソッドに使う |
@staticmethod |
インスタンスにもクラスにも依存しないメソッドに使う |
@property |
メソッドを属性のように呼び出せるようにする |
@dataclass |
クラスに__init__などを自動生成する |
この4つはPython標準で用意されているデコレータなので、自分で定義せずにそのまま使えます。
classmethodの基本
@classmethodは、インスタンス(クラスから作ったオブジェクト)ではなく、クラスそのものを第一引数として受け取るメソッドに使うデコレータです。
通常のメソッドは、呼び出すときにインスタンスが必要です。しかし、@classmethodを付けたメソッドは、インスタンスを作らなくてもクラス名から直接呼び出せます。
通常のメソッドの第一引数はself(インスタンス)ですが、@classmethodではcls(クラス自体)を受け取ります。
インスタンスを作る前にクラスの情報を使いたいという場面で役立ちます。
出力結果
このクラスの動物は イルカ です処理の流れを確認しておきましょう。
- Animal.show_name()が呼び出される
- @classmethodにより、第一引数clsにはインスタンスではなくAnimalクラス自体が渡される
- cls.nameでAnimalクラスのname属性("イルカ")にアクセスし、メッセージが表示される
通常のメソッドであればa = Animal()のようにインスタンスを作ってからa.show_name()と呼び出す必要がありますが、@classmethodを使うとその手順を省けます。
dataclassとの関係
クラスを定義するとき、通常は__init__(初期化メソッド)を自分で書く必要があります。しかし、データをまとめて管理したいだけのシンプルなクラスでは、この書き方が少し手間です。
@dataclassを使うと、__init__や__repr__(オブジェクトを文字列として表現するメソッド)などを自動で生成してくれます。@dataclassはfrom dataclasses import dataclassでインポートして使います。
次のコードでは、@dataclassを使ってデータを管理するクラスを作っています。
出力結果
Animal(name='カワウソ', age=3)処理の流れは、次のようになっています。
- @dataclassを付けることで、Animalクラスに__init__や__repr__などのメソッドが自動で追加される
- Animal(name="カワウソ", age=3) でインスタンスを作成する。自動生成された__init__が呼び出され、nameとageがセットされる
- print(a) で自動生成された__repr__が呼び出され、Animal(name='カワウソ', age=3)という形式で内容が表示される
@dataclassがなければdef __init__(self, name, age):を自分で書く必要がありますが、@dataclassを付けるだけでその手間が省けます。
データをまとめて管理するだけのシンプルなクラスでは特に便利です。
よくある質問(Q&A)
Q. functools.wrapsとは何ですか?
A. 通常は、デコレータを使うと元の関数の名前や説明(docstring)が内側の関数のものに上書きされてしまいます。しかし、functools.wraps(func)を内側の関数に付けると、元の関数の情報を保持したまま使えます。デバッグやドキュメント生成の際に有用です。
Q. デコレータとクロージャはどう違いますか?
A. クロージャは、外側の関数の変数を内側の関数が記憶する仕組みのことです。
デコレータはクロージャの仕組みを活用して作られていますが、目的は「関数に処理を追加すること」です。クロージャはより広い概念で、デコレータはその応用のひとつと考えられます。
Q. デコレータはクラスに対しても使えますか?
A. はい、関数だけでなくクラスに対してもデコレータを適用できます。@dataclass がその代表例で、クラスそのものを引数として受け取り、加工して返すという点で関数のデコレータと同じ仕組みで動いています。
Q. デコレータの適用を一時的に外すことはできますか?
A. @記法を使わず、greet = my_decorator(greet)のように手動で適用した場合は変数を元に戻せますが、@記法で適用した場合は通常そのまま使い続ける前提です。
一時的に外したいときは条件で分岐させるか、デコレータを使わずに直接書く設計にした方がシンプルです。
Q. デコレータの中でエラーが起きたらどうなりますか?
A. 内側の関数の中でエラーが発生した場合、通常のエラーと同じようにトレースバックが表示されます。ただし、内側の関数が元の関数を呼んでいるため、エラーの発生箇所がわかりにくくなることがあります。functools.wrapsを使うと関数名などが保持されて、エラー内容を読みやすくなります。
まとめ
デコレータは、関数の中身を変えることなく処理を追加できる便利な仕組みです。
最初は「関数の中に関数を書く」点や「関数を戻り値として返す」という書き方に戸惑いを感じる人も多いですが、役割と構造を順番に理解していくと、自然と読み書きできるようになります。
デコレータが有用なのは、次のような場面です。
デコレータが活躍する場面
- 複数の関数に共通処理をまとめて適用したいとき
- ログ出力や実行時間の計測など、本体と切り離して管理したい処理があるとき
- 権限確認や入力チェックなど、関数の入り口で行う処理を統一したいとき
この記事の重要なポイントを振り返ります。
重要なポイント
- デコレータは、関数の中身を変えずに外側から処理を追加できる仕組み
- デコレータの基本構造は「外側の関数」「内側の関数」「内側の関数を返す」の3つ
- *argsと**kwargsを使うと、引数の形に依存しない汎用的なデコレータが作れる
- 元の関数が値を返す場合は、内側の関数でもreturnを忘れずに書く
- 規模が小さいコードや一度しか使わない処理には、無理にデコレータを使わなくてよい
今回の記事の内容は、難しいと感じた方も多いかもしれません。
なぜなら、デコレータは「プログラムを実行する前」に、関数の名前や中身が裏側で自動的に書き換えられてしまう仕組みだからです。
普通にコードを上から下に読んだだけでは、変数の中身がいつ、どこで、何にすり替わったのか、プロセスの順番が直感的に見えにくくなっています。
しかし、デコレータをマスターできると、同じ処理を何度も書かずに済むため、コードの見通しがよくなります。それは同時に、修正や機能追加がしやすい保守性の高いプログラムを書けるということです。
まずは、この記事で紹介した最小構成のデコレータを自分で書いてみてください。少しずつ機能を加えながら慣れていくのがおすすめです。
ぜひ、実際に繰り返しコードを書いて、スキルを磨いていきましょう。