読み込み中...

Pythonでデコレータを完全マスター!その作り方と応用例10選

Pythonでデコレータを学ぶ初心者が画面を見つめている Python
この記事は約31分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Pythonのデコレータは、関数やメソッドの前後に共通処理を差し込むための構文です。ログ出力、権限確認、例外処理、キャッシュなどを関数本体から切り離せるため、プログラミングの学習が進むほど出会う場面が増えます。

初心者がつまずきやすいのは、@decoratorの記号そのものではなく、関数を受け取り関数を返すという考え方です。その構造を押さえると、デコレータの作り方は一気に読み解きやすくなります。

このチュートリアルでは、Pythonの基礎からデコレータの応用例まで、サンプルコードを保ちながら整理するのが基本です。コーディング中に使う判断基準も含め、学習用の小さな例から業務アプリで見かける設計までつなげて理解できます。

動作確認環境
  • Python 3.12系
  • 標準ライブラリ: functoolstimerandomsqlite3
📖 この記事で学べること
  • Pythonの関数とデコレータの関係
  • デコレータの基本的な作り方と読み方
  • ログ、計測、認証、キャッシュなどの応用例
  • functools.wrapsを使う理由
  • 初心者が避けたいデコレータ設計の落とし穴

Pythonとは

Pythonは、読みやすい文法と豊富な標準ライブラリを備えたプログラミング言語です。defで関数を定義し、returnで値を返し、importで標準ライブラリや外部パッケージを読み込む構成が中心になります。

一般にPythonは、Webアプリ、データ分析、自動化、機械学習、教育用途などで広く使われます。関数を値として扱えるため、デコレータのような抽象化も比較的少ない記述量で表現できるのが目安です。

公式ドキュメントによれば、関数定義ではデコレータを関数定義の前に置けます。構文の詳細はPython公式ドキュメントの関数定義で確認できます。

Pythonの基礎から固めたい場合は、Python初心者のための完全ガイドも合わせて読むと、関数、モジュール、実行環境の流れを整理しやすくなるのがポイントです。デコレータの学習は、その上に載る応用的なコーディング技術です。

項目使う構文役割デコレータとの関係
関数定義def処理を名前付きでまとめます修飾対象になります
戻り値return処理結果を呼び出し元へ返しますwrapperから返す値を保ちます
位置引数*args任意個数の引数を受けます元関数へ引数を透過します
キーワード引数**kwargs名前付き引数を受けます汎用的な作り方に使います
関数名__name__関数の名前を参照しますログ出力で使います
ドキュメント__doc__説明文を保持しますwrapsなしでは失われます
例外処理try/except失敗時の分岐を扱います共通のエラー処理に使います
例外送出raise例外を発生させます入力チェックで使います
型判定isinstance値の型を調べます引数検証に使います
時間取得time.time現在時刻を秒で得ます処理時間計測に使います
待機time.sleep処理を一時停止しますリトライ間隔に使います
乱数random.choice候補から値を選びます失敗再現の例に使います
辞書dictキーと値を保持しますキャッシュ格納に使います
リストlist複数値を順番に持ちます権限一覧に使います
デコレータ構文@関数に修飾を適用します呼び出し前後を包みます
ラッパーwrapper元関数を包む関数です追加処理の置き場所です
元関数func修飾される関数ですデコレータの引数になります
メタ情報保持functools.wraps名前や説明を維持します実運用寄りの作り方で使います
標準出力print文字列や値を出しますサンプルコードの確認に使います
入力input端末から文字を受けます認証例で使います
SQLite接続sqlite3.connectDBへ接続しますトランザクション例で使います
SQL実行executeSQLを送りますBEGINCOMMITに使います
確定COMMIT変更を確定します正常終了時に使います
取消ROLLBACK変更を戻します例外発生時に使います
カーソルcursorDB操作の窓口です挿入処理で使います
行IDlastrowid追加行のIDを得ますDB処理の返り値に使います
属性参照.オブジェクトの要素を参照しますfunc.__name__で使います
文字列整形f-string値を文字列に埋め込みますログ文に使います
比較==値の一致を調べます認証条件に使います
メモ化cache結果を再利用します再帰処理の応用例です

デコレータとは

デコレータは、関数やメソッドやクラスに追加処理をまとわせる仕組みです。Pythonでは@デコレータ名を関数定義の直前に書くことで、関数オブジェクトを別の関数で包みます。

これにより、元の関数本体を直接書き換えずに、ログ、認証、計測、キャッシュ、例外変換などを加えられます。デコレータの作り方を理解するには、funcを受け取ってwrapperを返す流れを追うのが近道です。

その構造は、decorated = decorator(original)という代入に近いと考えると整理できるのが一般的です。@decoratorは見た目を短くする構文であり、内部では関数オブジェクトの差し替えが起きます。

公式ドキュメントでは、functools.wrapsがラッパー関数へ元関数のメタデータをコピーする補助として説明されています。詳しくはPython公式ドキュメントのfunctools.wrapsを参照できるのが現実的です。

💡 Tips: 初心者は、デコレータを「関数の前後に処理を足す記法」と覚えるより、「関数を受け取って別の関数を返す作り方」と覚えるほうが応用例を読みやすくなります。

Pythonでデコレータを使う理由

Pythonでデコレータを使う理由は、複数の関数に共通する処理を一か所へ集められる点にあります。たとえば、ログ出力を全関数に直接書くと、コーディング量が増え、修正時の漏れも起きやすくなると整理できます。

その共通処理をデコレータにすると、元関数には本来の処理だけを残せます。プログラミングでは、関心ごとを分けるほど読みやすさと変更しやすさが上がるため、デコレータは設計を整理する道具になります。

一方で、何でもデコレータ化すればよいわけではありません。処理の流れが見えにくくなる場合もあるため、ログ、権限、計測、再試行のように横断的な処理へ絞るのが現実的です。

自動化やウィンドウ操作のように処理の前後で状態確認をしたい場合は、Pythonで実現するウィンドウ操作の自動化のようなテーマとも相性があると理解できます。処理本体と監視処理を分ける発想が、デコレータの応用例にもつながります。

Pythonでのデコレータの基本的な作り方

Pythonでの基本的な作り方は、外側の関数で元関数を受け取り、内側のwrapper_funcで前処理、元関数の呼び出し、後処理を並べる形です。最小の結論は、デコレータはreturn wrapper_funcで新しい関数を返すという点になります。

デコレータの基本形

具体的には、次のサンプルコードで関数の前後に文字列を出力すると覚えるとよいでしょう。*args**kwargsを受けることで、引数の形が異なる関数にも同じ作り方を使えます。

def decorator_func(original_func):
    def wrapper_func(*args, **kwargs):
        # 追加したい処理
        print('Before calling the original function')

        # 元の関数の呼び出し
        result = original_func(*args, **kwargs)

        # 追加したい処理
        print('After calling the original function')

        return result

    return wrapper_func

結果: 期待される動きとして、元関数を呼ぶ前にBefore calling the original function、呼んだ後にAfter calling the original functionを出す関数が作られます。

このコードでは、decorator_funcが元関数であるoriginal_funcを受け取ります。その内側で定義したwrapper_funcが実際に呼ばれる関数になり、最後にreturn wrapper_funcで返されますし、ここがポイントです。

このとき、resultに元関数の戻り値を入れて返すことが大切です。戻り値を返さない作り方にすると、元関数が本来返す値を呼び出し元が受け取れなくなります。

デコレータの使い方

作成したデコレータは、対象関数の直前に@decorator_funcとして置きます。この記法により、greetingは元の関数そのものではなく、decorator_funcが返した関数として扱われますが、これは押さえたい点です。

@decorator_func
def greeting():
    print('Hello, world!')

結果: 期待される動きとして、greetingを呼ぶ準備が整い、関数本体の前後にデコレータ側の処理が差し込まれます。

これを呼び出すと、greetingの本体にあるprintだけでなく、ラッパー関数に書いた前処理と後処理も動きます。チュートリアルとしては、@の行が関数の意味を変える入口だと理解できると考えられます。

Before calling the original function
Hello, world!
After calling the original function

結果: 期待される出力例は、前処理、元関数の表示、後処理の順に並びます。これは出力例であり、実際のプログラムでは前後の処理内容を用途に合わせて変えます。

そのため、デコレータの学習では、構文よりも呼び出し順の理解が軸になると言えるでしょう。関数を変数に入れたり返したりできるPythonの性質が、デコレータの作り方を支えています。

デコレータの応用例とサンプルコード10選

デコレータの応用例は、関数本体に混ぜたくない横断処理に集まります。ログ、処理時間、引数チェック、キャッシュ、リトライ、デバッグ、トランザクション、認証、権限、エラーハンドリングは、サンプルコードで動きの差を比べやすい題材です。

これらの応用例では、共通してwrapperが元関数を包みますし、これが一つの目安です。一方で、引数を受けるデコレータ、外部状態を参照するデコレータ、例外を扱うデコレータでは、作り方の注意点が少しずつ変わります。

サンプルコード1:ログ出力デコレータ

ログ出力デコレータは、関数が呼ばれた事実を記録したいときに使います。関数本体へ毎回printを書くより、共通の入口としてまとめるほうがコーディングの見通しを保てますが、覚えておくと役立つでしょう。

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f'関数"{func.__name__}"が呼び出されました')
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def greet(name):
    print(f'こんにちは、{name}さん!')

greet('山田')

結果: 期待される出力は、関数名のログに続いてこんにちは、山田さん!が表示される形です。

このサンプルコードでは、func.__name__で元関数の名前を取得しています。greetの中身は挨拶だけに保たれ、呼び出し記録はlog_decorator側へ分離されます。

ただし、実際のアプリではprintではなくloggingモジュールを使うほうが一般的です。小さなチュートリアルではprintのほうが流れを追いやすいため、最初の学習例として適しているのが基本です。

サンプルコード2:計算時間計測デコレータ

処理時間を見たい関数が複数ある場合、計測処理をデコレータにできます。time.timeで開始時刻と終了時刻を取り、その差分を出す作り方です。

import time

def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'関数"{func.__name__}"の実行時間:{end - start}秒')
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(2)

slow_function()

結果: 期待される出力は、slow_functionの処理時間を示すログです。環境や負荷により秒数は前後します。

このコードでは、startendの差が処理時間として表示されます。time.sleep(2)により約2秒待機するため、チュートリアル用の変化が見えやすくなるのが目安です。

一方で、厳密な性能計測にはtime.perf_counterや専用ツールを検討します。ここでの目的はデコレータの応用例を学ぶことであり、性能評価そのものではありません。

サンプルコード3:引数チェックデコレータ

引数チェックは、関数に不正な値が渡されたときに早めに失敗させる考え方です。デコレータにすると、同じ入力条件を複数の関数へ適用できます。

def argument_check_decorator(func):
    def wrapper(arg1, arg2):
        if not isinstance(arg1, int) or not isinstance(arg2, int):
            raise TypeError('引数はすべて整数でなければなりません')
        return func(arg1, arg2)
    return wrapper

@argument_check_decorator
def add(arg1, arg2):
    return arg1 + arg2

print(add(1, 2))  # 正しく実行され、結果として3が出力されます
print(add('1', 2))  # TypeErrorが発生します

結果: 期待される出力は、最初の呼び出しで3が表示され、次の呼び出しでTypeErrorが発生する流れです。

このサンプルコードでは、isinstanceintかどうかを確認します。条件に合わない場合はraise TypeErrorにより、関数本体の処理へ進む前に失敗させます。

ただし、wrapper(arg1, arg2)の形では引数が2個の関数にしか合いません。汎用的にしたい場合は、*argsを走査する作り方へ変えると扱いやすくなるのがポイントです。

サンプルコード4:キャッシュデコレータ

キャッシュデコレータは、同じ引数で同じ結果になる関数に向いています。再帰的なフィボナッチ計算のように、同じ計算が何度も起きる処理では考え方をつかみやすい応用例です。

import functools

def cache_decorator(func):
    cache = dict()
    @functools.wraps(func)
    def wrapper(n):
        if n not in cache:
            cache[n] = func(n)
        return cache[n]
    return wrapper

@cache_decorator
def fib(n):
    if n < 2:
        return n
    else:
        return fib(n-1) + fib(n-2)

print(fib(10))  # 55と出力されます

結果: 期待される出力は55です。同じnで呼ばれた計算結果はcacheに保存されます。

このコードでは、cache辞書にnをキーとして結果を保存します。n not in cacheのときだけ元関数を呼び、保存済みなら辞書から返するのが一般的です。

一般に、実用的なメモ化には標準ライブラリのfunctools.lru_cacheも選択肢になります。学習段階では自作の作り方を読むことで、キャッシュがどこに保存されるかを理解できます。

データを表で扱う処理でも、同じ変換を繰り返すならキャッシュの発想が役立ちますし、ここを基本と考えるとよいでしょう。表操作の基礎はPythonで表を操作するための詳細ガイドで整理できます。

サンプルコード5:リトライ機能付きデコレータ

リトライ機能付きデコレータは、一時的な失敗が起こりうる処理に使われます。ネットワーク通信や外部API呼び出しのように、再試行で成功する可能性がある処理が対象になるのが現実的です。

import time
import random

def retry_decorator(max_retries=3, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    time.sleep(delay)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@retry_decorator(max_retries=5, delay=2)
def get_data():
    print("データ取得を試みます...")
    if random.choice([True, False]):
        print("データ取得成功!")
        return "データ"
    else:
        print("データ取得失敗...")
        raise Exception("データ取得エラー")

print(get_data())

結果: 期待される出力はランダムに変わります。成功時はデータが返り、失敗が続く場合は最後の呼び出しで例外が発生します。

この作り方では、retry_decoratormax_retriesdelayを受け取り、内側のdecoratorを返すると整理できます。さらにdecoratorが関数を受け取り、wrapperで再試行を制御します。

ただし、すべてのExceptionを握る作り方は原因を隠す場合があります。実際の設計では、再試行してよい例外だけを指定し、ログを残す構成が扱いやすくなると理解できます。

サンプルコード6:デバッグ情報出力デコレータ

デバッグ情報出力デコレータは、関数名、引数、処理時間をまとめて表示する例です。コーディング中に呼び出しの流れを追いたいとき、処理本体へ確認用コードを散らさずに済みます。

import time

def debug_info(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'Function: {func.__name__}, Arguments: {args, kwargs}, Execution time: {end_time - start_time} seconds')
        return result
    return wrapper

@debug_info
def add_numbers(x, y):
    time.sleep(2) # simulate a time-consuming process
    return x + y

print(add_numbers(3, 4))

結果: 期待される出力は、関数名、引数、処理時間の行に続いて7が表示される形です。秒数は環境により変わります。

このサンプルコードでは、argskwargsをそのまま出力するため、呼び出し時の値を確認できます。resultを返すことで、デコレータを付けても元関数の戻り値を保てますし、ここがポイントです。

Function: add_numbers, Arguments: ((3, 4), {}), Execution time: 2.002042293548584 seconds
7

結果: 期待される出力例は、処理時間の数値が多少変わる点を除き、関数情報と戻り値が順に並ぶ形です。

その表示から、add_numbers34を受け取り、合計の7を返す流れを追えます。学習用のデバッグでは十分ですが、長期運用するコードではログレベルや出力先も設計します。

サンプルコード7:トランザクション制御デコレータ

トランザクション制御デコレータは、データベース操作の開始、確定、取消を一か所にまとめる応用例です。関数本体にはデータ挿入などの処理を残し、失敗時のROLLBACKをデコレータ側で扱いると覚えるとよいでしょう。

⚠️ 注意: 実際のWebアプリでは接続管理、例外の再送出、認可、監査ログなども合わせて設計します。このサンプルコードは、デコレータの構造を学ぶための最小構成です。
import sqlite3
from sqlite3 import Error

def transaction_control(func):
    def wrapper(*args, **kwargs):
        conn = None
        try:
            conn = sqlite3.connect(":memory:") # connect to a database
            conn.execute("BEGIN") # start a transaction
            result = func(conn, *args, **kwargs)
            conn.execute("COMMIT") # commit if no errors
        except Error as e:
            if conn:
                conn.execute("ROLLBACK") # rollback in case of error
            print(f"An error '{e}' occurred during the transaction.")
            result = None
        finally:
            if conn:
                conn.close() # close the connection
        return result
    return wrapper

@transaction_control
def insert_data(conn, data):
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, data TEXT)")
    cursor.execute("INSERT INTO test(data) VALUES(?)", (data,))
    return cursor.lastrowid

print(insert_data("test data"))

結果: 期待される出力は、挿入に成功した行のIDです。エラー時はエラーメッセージが表示され、戻り値はNoneになります。

このコードでは、sqlite3.connectでメモリ上のDBへ接続し、BEGINでトランザクションを開始します。関数が成功すればCOMMITsqlite3.Errorが起きればROLLBACKを実行すると考えられます。

一方で、例外を表示してNoneを返す作り方は、呼び出し元が失敗理由を扱いにくくなる場合があります。実用コードでは、必要に応じて例外を再送出する設計も検討します。

サンプルコード8:ユーザ認証デコレータ

ユーザ認証デコレータは、関数を実行する前に利用者を確認する例です。Webアプリの認証処理はフレームワーク側で提供されることが多いものの、デコレータの考え方を理解する題材として適していると言えるでしょう。

def auth_required(func):
    def wrapper(*args, **kwargs):
        username = input("Username: ")
        password = input("Password: ")
        if username == 'admin' and password == 'secret':
            result = func(*args, **kwargs)
        else:
            result = "Invalid username or password!"
        return result
    return wrapper

@auth_required
def sensitive_operation():
    return "Succeeded in sensitive operation!"

print(sensitive_operation())

結果: 期待される出力は、認証情報が一致した場合にSucceeded in sensitive operation!、一致しない場合にInvalid username or password!です。

このサンプルコードでは、inputでユーザ名とパスワードを受け取り、固定値と比較します。username == 'admin'かつpassword == 'secret'のときだけ、元関数を呼びます。

ただし、パスワードをコードへ直接書く作り方は避けますが、これは押さえたい点です。実際のプログラミングでは、ハッシュ化、セッション管理、CSRF対策、監査ログなどが必要になります。

サンプルコード9:権限チェックデコレータ

認証が利用者の確認なら、権限チェックは操作できる範囲の確認です。デコレータへ必要な権限を渡す作り方にすると、読み取り、書き込み、管理などの条件を関数ごとに分けられます。

def permission_required(permission):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if permission in user_permissions:
                result = func(*args, **kwargs)
            else:
                result = "You do not have permission to perform this operation!"
            return result
        return wrapper
    return decorator

user_permissions = ['read', 'write']

@permission_required('write')
def write_operation():
    return "Succeeded in write operation!"

print(write_operation())

結果: 期待される出力は、write権限がある場合にSucceeded in write operation!です。権限がなければ拒否メッセージが返ります。

このコードでは、permission_required('write')がデコレータを生成します。つまり、引数付きデコレータの作り方になっており、外側で権限名を受け、内側で関数を包みますし、これが一つの目安です。

実際のWebアプリでは、user_permissionsのようなグローバル変数ではなく、ログイン中ユーザのセッションやトークンから権限を取り出す構成が一般的です。デコレータは、その判定をルート関数やビュー関数の前に置く考え方と相性があります。

サンプルコード10:エラーハンドリングデコレータ

エラーハンドリングデコレータは、関数内で起きる例外を捕まえてメッセージへ変換する例です。複数の関数で同じtry/exceptを書く必要がある場合、共通化の候補になります。

def error_handler(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            return f"Error: {str(e)}"
    return wrapper

@error_handler
def risky_operation(x, y):
    return x / y

print(risky_operation(10, 0))

結果: 期待される出力はError: division by zeroです。10 / 0によりZeroDivisionErrorが発生し、文字列として返されます。

このサンプルコードでは、except Exception as eで例外を受け取り、str(e)で文字列化しています。呼び出し元は例外ではなく戻り値を受け取るため、扱い方が変わりますが、覚えておくと役立つでしょう。

ただし、例外をすべて文字列へ変えると、エラーを検知したい上位処理が失敗を見落とす場合があります。APIやバッチ処理では、ログを残して例外を再送出する作り方も候補になります。

グラフ作成やファイル出力でも、例外処理を共通化したい場面があるのが基本です。可視化処理の流れはPythonで折れ線グラフ作成の完全ガイドで確認できます。

デコレータの注意点

デコレータは関数の見た目を短くできますが、処理が外側から差し込まれるため、読み手が呼び出し順を追いにくくなる場合があります。初心者は、デコレータが適用される順序と、関数メタデータの変化を押さえると安全に学習できるのが目安です。

複数のデコレータを重ねる場合、下にあるデコレータから先に関数へ適用されます。その後、呼び出し時には外側のラッパーから処理が始まるため、見た目と実行順を分けて考える必要があります。

@decorator1
@decorator2
def function():
    pass

結果: 期待される解釈は、function = decorator1(decorator2(function))に近い形です。decorator2で包まれた関数を、さらにdecorator1が包みますし、ここを基本と考えるとよいでしょう。

この順序は、認証とログのように意味が異なるデコレータを重ねると結果に影響します。たとえば、認証前のアクセスもログに残すのか、認証成功後だけ記録するのかで並べ方が変わります。

もう一つの注意点は、ラッパー関数により元関数の__name____doc__が失われることです。ドキュメント生成やテスト出力で関数名を使う場合、この副作用が問題になる可能性があるのがポイントです。

def decorator(func):
    def wrapper():
        return func()
    return wrapper

@decorator
def hello_world():
    """This function returns the string 'Hello, World!'"""
    return 'Hello, World!'

print(hello_world.__name__)
print(hello_world.__doc__)

結果: 期待される出力は、関数名がwrapper、ドキュメント文字列がNoneになる形です。元のhello_worldの情報がラッパーで隠れます。

この問題には、functools.wrapsを使う作り方が適しています。@wraps(func)wrapperへ付けると、元関数の名前やドキュメントを維持できるのが一般的です。

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper():
        return func()
    return wrapper

@decorator
def hello_world():
    """This function returns the string 'Hello, World!'"""
    return 'Hello, World!'

print(hello_world.__name__)
print(hello_world.__doc__)

結果: 期待される出力は、関数名がhello_world、ドキュメント文字列が元の説明文になる形です。wrapsによりメタデータが保たれます。

そのため、公開するライブラリや長く保守するコードでは、デコレータを書くたびにfunctools.wrapsの利用を検討します。サンプルコードの短さより、後から読む人が関数の正体を追えることを優先するのが現実的です。

ℹ️ 補足: デコレータは関数の外側から動きを変えるため、テストでは「デコレータ付きの挙動」と「元関数の本来の挙動」を分けて確認すると整理しやすくなります。

デコレータのカスタマイズ方法

デコレータをカスタマイズしたい場合は、デコレータそのものへ引数を渡す構造にします。通常のデコレータは関数を受け取りますが、引数付きデコレータでは外側にもう一段関数を置きますし、ここがポイントです。

この外側の関数は、デコレータを作る関数として働きます。たとえば、ログの文言、リトライ回数、権限名、キャッシュの有効期限などを外から渡すと、同じ作り方で複数の挙動を表現できます。

具体的には、decorator_factoryが設定値を受け取り、内側のdecoratorが元関数を受け取りますが、これは押さえたい点です。さらにwrapperが実行時の引数を受け取るため、合計で三層構造になります。

def decorator_factory(argument):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"前処理 {argument}")
            result = func(*args, **kwargs)
            print(f"後処理 {argument}")
            return result
        return wrapper
    return decorator

@decorator_factory("カスタムメッセージ")
def add(x, y):
    return x + y

結果: 期待される動きとして、addの前後にカスタムメッセージを含む処理が差し込まれます。まだ呼び出していないため、表示は発生しません。

このサンプルコードでは、argumentが外側の関数に閉じ込められ、wrapperの中から参照されます。これはクロージャの働きであり、デコレータの応用例を支える考え方です。

print(add(2, 3))

結果: 期待される動きとして、前処理、後処理、戻り値の表示が順番に発生すると整理できます。add(2, 3)の戻り値は5です。

前処理 カスタムメッセージ
後処理 カスタムメッセージ
5

結果: 期待される出力例は、前処理の文、後処理の文、計算結果の5が並ぶ形です。出力順はwrapper内の処理順に従います。

この作り方を使うと、同じデコレータの型を保ったまま、関数ごとにメッセージや条件を変えられます。たとえば改行の有無を切り替える処理では、Pythonで改行あり・なしを制御する方法のような入出力制御と組み合わせて考えられますし、これが一つの目安です。

ただし、引数付きデコレータは階層が深くなります。初心者の学習では、通常のデコレータ、wraps付きのデコレータ、引数付きデコレータの順で読むと、構造を取り違えにくくなります。

まとめ

Pythonのデコレータは、関数を受け取り、前後の処理を追加した関数を返す仕組みです。作り方の中心はwrapperであり、*args**kwargsreturn resultを正しく扱うことで、元関数の振る舞いを保ちながら処理を加えられますが、覚えておくと役立つでしょう。

応用例としては、ログ出力、時間計測、引数チェック、キャッシュ、リトライ、デバッグ情報、トランザクション、認証、権限、エラーハンドリングが代表的です。これらは関数本体へ直接混ぜるより、横断的な処理として分離したほうが読みやすくなる場面があります。

一方で、デコレータを重ねる順序、例外の扱い、関数メタデータの消失には注意が必要です。functools.wrapsを使い、デコレータの責務を小さく保つと、後から読む人にも意図が伝わりやすくなります。

学習では、短いサンプルコードを写すだけでなく、どの関数がどの関数を受け取り、どの関数を返しているかを線で追うと理解が進みますし、ここを基本と考えるとよいでしょう。チュートリアルの範囲を超えて実務的なコーディングへ進む際も、この読み方がデバッグや設計の助けになります。

Pythonプログラミングでデコレータを使いこなすには、すべてを装飾するのではなく、共通処理を切り出したい場面に絞る判断が欠かせません。作り方、応用例、注意点を合わせて押さえることで、サンプルコードを自分のコードへ無理なく移せます。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。