Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

Python辞典

  1. トップページ
  2. Python辞典
  3. def デコレータ / @構文

def デコレータ / @構文

デコレータは関数やクラスを受け取り、機能を追加した新しい関数(またはクラス)を返す仕組みです。『@デコレータ名』の構文で関数定義の直前に書くと、関数が定義された時点でデコレータが適用されます。デコレータを自作するときは必ず『functools.wraps()』を使って元の関数のメタデータを保持しましょう

構文
from functools import wraps

# デコレータの定義
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 前処理
        result = func(*args, **kwargs)
        # 後処理
        return result
    return wrapper

# デコレータの適用
@my_decorator
def my_function():
    pass

# 引数付きデコレータ
def decorator_with_args(arg1, arg2):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator
構文・関数一覧
構文・関数概要
@デコレータ名関数定義の前に書いてデコレータを適用する。
@wraps(func)wrapperに元のfuncのメタデータ(__name__など)をコピーする。
*args, **kwargsデコレータ内でラップする関数のすべての引数を受け取るパターン。
@deco1 @deco2デコレータのスタック。下から順に適用される(deco2→deco1の順)。
サンプルコード
from functools import wraps
import time

# 実行時間を計測するデコレータ
def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"[{func.__name__}] 実行時間: {elapsed:.4f}秒")
        return result
    return wrapper

@timer
def heavy_process(n):
    """重い処理のシミュレーション"""
    return sum(range(n))

print(heavy_process(1000000))   # [heavy_process] 実行時間: 0.XXXX秒

# 引数付きデコレータ(繰り返し実行)
def retry(times=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"試行 {i+1}/{times} 失敗: {e}")
            raise RuntimeError(f"{func.__name__} が{times}回失敗しました")
        return wrapper
    return decorator

@retry(times=3)
def unstable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("接続エラー")
    return "成功"

# デコレータのスタック(複数のデコレータを重ねる)
def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"**{func(*args, **kwargs)}**"
    return wrapper

def upper(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

@bold       # 2番目に適用(外側)
@upper      # 1番目に適用(内側)
def greet(name):
    return f"hello, {name}"

print(greet('world'))   # **HELLO, WORLD**(upper → bold の順で適用)

# クラスベースのデコレータ
class Memoize:
    def __init__(self, func):
        self.func  = func
        self.cache = {}
        wraps(func)(self)   # メタデータをコピー

    def __call__(self, *args):
        if args not in self.cache:
            self.cache[args] = self.func(*args)
        return self.cache[args]

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

print(fib(30))  # 832040(キャッシュで高速)
概要

デコレータはPythonのシンタックスシュガーで、『@my_decorator』を書くことは『func = my_decorator(func)』と等価です。デコレータを重ねた場合は下から順(内側から外側)に適用されます。

引数付きデコレータは3重の関数定義になりますが、構造を理解すれば複雑ではありません。外側の関数で引数を受け取り、中間の関数でfuncを受け取り、内側のwrapperで実際の処理をラップします。

デコレータはログ記録・認証チェック・キャッシュ・バリデーションなど、横断的な関心事(クロスカッティングコンサーン)の実装に最適で、コードの重複を避けるDRY原則の実現に役立ちます

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。