閉じる

【Python】デコレータの使い方|基礎から実装まで

Pythonのデコレータは、関数やメソッド、クラスの「前後に処理を差し込む」ための強力な仕組みです。

仕組み自体はシンプルですが、最初は少し抽象的に感じやすい概念でもあります。

本記事では、デコレータの本質である「関数を返す関数」という考え方から、実務で役立つ活用パターンまで、段階的に丁寧に解説していきます。

デコレータとは

デコレータの基本構文と仕組み

デコレータとは、関数やメソッド、クラスに対して「別の関数でラップする」ための仕組みです。

Pythonでは、関数を第一級オブジェクトとして扱えるため、関数を引数に渡したり、戻り値として返すことができます。

この性質を利用して、元の関数に手を加えずに機能を拡張できるのがデコレータです。

Pythonでは、次の2つのことが同時に成り立っています。

  • 関数はオブジェクトであり、変数に代入したり、引数に渡したりできる
  • 関数の定義時に@decoratorという特殊な構文で、別の関数を適用できる

これにより、例えば「ログ出力を自動で行う関数」「実行時間を計測する関数」などを、毎回コードを書き足すことなく実現できます。

デコレータの最もシンプルなイメージ

デコレータは、概念的には次のようなイメージです。

  • 元の関数: func
  • デコレータ: decorator
  • ラップされた新しい関数: wrapped_func

デコレータは「関数を受け取り、新しい関数を返す関数」だと理解するとわかりやすくなります。

関数を引数に取る高階関数との関係

デコレータの理解には、高階関数(high-order function)という考え方が重要です。

高階関数とは、次のような関数のことを指します。

  • 関数を引数にとる関数
  • 関数を戻り値として返す関数

Python標準のmapfilterも高階関数の一種です。

Python
def square(x):
    return x * x

# square 関数を引数として map に渡している
result = map(square, [1, 2, 3, 4])
print(list(result))  # [1, 4, 9, 16]

デコレータ関数は、「関数を引数にとって、関数を返す」高階関数として実装されます。

つまり、高階関数の一種であり、そのうち「関数定義にかぶせて使うための特別な用途を持つもの」がデコレータだと言えます。

@記法(シンタックスシュガー)の意味

Pythonには、デコレータ専用の簡略記法があります。

それが@decorator_nameというシンタックスシュガーです。

次の2つのコードは、意味的に同じです。

Python
def my_decorator(func):
    def wrapper():
        print("前処理")
        result = func()
        print("後処理")
        return result
    return wrapper

# 1. @記法を使った書き方
@my_decorator
def hello():
    print("こんにちは")

# 2. @記法を使わない場合と等価な書き方
def hello2():
    print("こんにちは")

hello2 = my_decorator(hello2)  # ここでデコレータを適用している
Python
hello()
hello2()
実行結果
前処理
こんにちは
後処理
前処理
こんにちは
後処理

@記法は、関数定義直後に「その関数をデコレータに通して、戻り値を同じ名前に代入する」操作を自動で行っているだけです。

この仕組みが理解できれば、デコレータの挙動をイメージしやすくなります。

デコレータの基礎的な使い方

関数デコレータの最小実装例

もっとも基本的なデコレータは、次の3要素で構成されます。

  1. デコレータ本体(外側の関数)
  2. ラップ用の内部関数(wrapper)
  3. 内部関数から元の関数を呼び出す処理
Python
def simple_decorator(func):
    """最もシンプルなデコレータの例"""

    def wrapper(*args, **kwargs):
        # 前処理
        print("関数の前で何かをします")

        # 元の関数を実行
        result = func(*args, **kwargs)

        # 後処理
        print("関数の後で何かをします")

        return result

    return wrapper


@simple_decorator
def greet(name):
    print(f"こんにちは、{name}さん")


greet("太郎")
実行結果
関数の前で何かをします
こんにちは、太郎さん
関数の後で何かをします

ポイントは、デコレータが「元の関数を受け取り、内部でその関数を呼び出す別の関数(wrapper)を返している」ことです

*args**kwargsを用いることで、引数の個数や名前に関係なく任意の関数をラップできます。

引数なしデコレータの書き方

これまで見てきた@simple_decoratorのように、デコレータ自体に引数を取らない場合は、先ほどの構造のままで問題ありません。

Python
def debug_print(func):
    """関数の引数と戻り値を表示するデコレータ"""

    def wrapper(*args, **kwargs):
        print(f"[DEBUG] 呼び出し: {func.__name__} args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"[DEBUG] 戻り値: {result}")
        return result

    return wrapper


@debug_print
def add(a, b):
    return a + b


add(3, 5)
実行結果
[DEBUG] 呼び出し: add args=(3, 5), kwargs={}
[DEBUG] 戻り値: 8

このように、引数なしデコレータは「1段階の関数」で完結しています。

引数ありデコレータの書き方

デコレータ自体にパラメータ(設定値)を渡したい場合は、さらに「もう1段階外側の関数」を用意する必要があります。

これをデコレータファクトリと呼ぶこともあります。

Python
def repeat(times):
    """指定回数だけ関数を繰り返し実行するデコレータ"""

    def decorator(func):
        def wrapper(*args, **kwargs):
            result = None
            for i in range(times):
                print(f"{i + 1}回目の実行")
                result = func(*args, **kwargs)
            return result

        return wrapper

    return decorator


@repeat(times=3)
def say(message):
    print(message)


say("こんにちは")
実行結果
1回目の実行
こんにちは
2回目の実行
こんにちは
3回目の実行
こんにちは

ここでは、次の順番で関数が返されていきます。

  1. repeat(3) が呼ばれ、decorator 関数を返す
  2. その decorator が、関数定義時に say を受け取り、wrapper を返す
  3. 実際の呼び出しでは wrapper が実行される

@repeat(3) という書き方は、say = repeat(3)(say) と同じ意味になります。

複数デコレータの適用順序

Pythonでは、1つの関数に対して複数のデコレータを重ねて適用できます。

このとき、適用順序は次のようになります。

Python
def deco1(func):
    def wrapper(*args, **kwargs):
        print("deco1: 前処理")
        result = func(*args, **kwargs)
        print("deco1: 後処理")
        return result
    return wrapper


def deco2(func):
    def wrapper(*args, **kwargs):
        print("deco2: 前処理")
        result = func(*args, **kwargs)
        print("deco2: 後処理")
        return result
    return wrapper


@deco1
@deco2
def process():
    print("メイン処理")


process()
実行結果
deco1: 前処理
deco2: 前処理
メイン処理
deco2: 後処理
deco1: 後処理

この例は、次のコードと同じ意味です。

Python
def process():
    print("メイン処理")

process = deco1(deco2(process))

一番下に書かれたデコレータが先に関数へ適用され、その結果に対して上のデコレータが順番に適用されるという順序を押さえておくと、複雑なデコレータチェーンも読みやすくなります。

よく使う標準デコレータの実装パターン

@staticmethodと@classmethodの違いと使い方

Pythonのクラス定義では、@staticmethod@classmethod がよく利用されます。

どちらもメソッドに付けるデコレータですが、その役割は異なります。

代表的な違いを表にまとめます。

種類第一引数アクセスできるものデコレータ
インスタンスメソッドselfインスタンス属性・クラス属性(デフォルト)
クラスメソッドclsクラス属性@classmethod
スタティックメソッドなし特に限定なし(普通の関数同様)@staticmethod

@staticmethod の例

Python
class MathUtil:
    @staticmethod
    def add(a, b):
        """インスタンスに依存しないユーティリティ関数"""
        return a + b


print(MathUtil.add(2, 3))  # 5

obj = MathUtil()
print(obj.add(4, 5))       # 9 (インスタンス経由でも呼べるが、selfは使わない)

スタティックメソッドは、クラスと関連はあるが、インスタンスにもクラス属性にも依存しない処理をまとめておくのに向いています。

@classmethod の例

Python
class Person:
    default_country = "Japan"

    def __init__(self, name, country=None):
        self.name = name
        self.country = country or Person.default_country

    @classmethod
    def from_japan(cls, name):
        """日本出身のPersonを生成するファクトリメソッド"""
        return cls(name, country="Japan")


p1 = Person("Taro")
p2 = Person.from_japan("Hanako")  # クラスメソッドからインスタンスを生成

print(p1.name, p1.country)  # Taro Japan
print(p2.name, p2.country)  # Hanako Japan

クラスメソッドは、クラス自体に対する操作(設定の変更やインスタンス生成パターンの追加など)に使うのが基本です。

@propertyでゲッターをデコレートする

@property は、メソッドを「読み取り専用の属性」のように見せるためのデコレータです。

内部ではメソッドとして実装しておきながら、外部からは単なる属性としてアクセスさせたい場合に便利です。

Python
class User:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        """フルネームを返すプロパティ"""
        return f"{self.last_name} {self.first_name}"


u = User("太郎", "山田")
print(u.full_name)  # メソッド呼び出しではなく属性アクセスに見える
実行結果
山田 太郎

このように@propertyを使うことで、内部実装を隠蔽しつつ、外部APIをシンプルに保つことができます。

また、@xxx.setter デコレータと組み合わせて、書き込み時のバリデーションなども行えます。

@functools.wrapsでメタデータを保持する

デコレータで関数をラップすると、元の関数の__name____doc__ などのメタデータが失われてしまいます。

これを防ぐためにfunctools.wrapsを利用します。

Python
import functools


def no_wraps_decorator(func):
    def wrapper(*args, **kwargs):
        """ラップ用関数(メタデータが上書きされる)"""
        return func(*args, **kwargs)
    return wrapper


def with_wraps_decorator(func):
    @functools.wraps(func)  # ここがポイント
    def wrapper(*args, **kwargs):
        """ラップ用関数(メタデータは元の関数のものを引き継ぐ)"""
        return func(*args, **kwargs)
    return wrapper


@no_wraps_decorator
def original_func1():
    """元の関数1のドキュメンテーション"""
    pass


@with_wraps_decorator
def original_func2():
    """元の関数2のドキュメンテーション"""
    pass


print(original_func1.__name__, original_func1.__doc__)
print(original_func2.__name__, original_func2.__doc__)
実行結果
wrapper ラップ用関数(メタデータが上書きされる)
original_func2 元の関数2のドキュメンテーション

ライブラリやフレームワークの実装、またはデバッグやドキュメント生成を考えると、wrapsの利用はほぼ必須です。

独自デコレータを実装する際は、基本的に@functools.wrapsを付ける習慣をつけておくと良いです。

ログ出力や計測用デコレータの実装例

ログ出力や処理時間の計測は、デコレータの典型的な用途です。

関数の本体を汚さずに、横断的な関心事を追加できます。

Python
import time
import functools


def log_and_time(func):
    """関数の開始・終了ログと実行時間を表示するデコレータ"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 開始: {func.__name__}")
        start = time.time()

        result = func(*args, **kwargs)

        elapsed = time.time() - start
        print(f"[LOG] 終了: {func.__name__} (経過時間: {elapsed:.4f} 秒)")
        return result

    return wrapper


@log_and_time
def slow_task():
    """時間のかかる処理を模した関数"""
    time.sleep(1.2)
    print("重い処理が完了しました")


slow_task()
実行結果
[LOG] 開始: slow_task
重い処理が完了しました
[LOG] 終了: slow_task (経過時間: 1.2000 秒)

このようにログ出力や計測用のデコレータを1つ作っておけば、任意の関数に簡単に適用できるため、デバッグや性能分析が効率的になります。

実践で役立つデコレータ活用パターン

バリデーションを行うデコレータの実装

実務では、関数の引数や戻り値に対してバリデーションを行いたい場面が多くあります。

これもデコレータで共通化できます。

Python
import functools


def ensure_positive(func):
    """すべての数値引数が0以上であることを確認するデコレータ"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 位置引数をチェック
        for value in args:
            if isinstance(value, (int, float)) and value < 0:
                raise ValueError(f"負の値は許可されていません: {value}")

        # キーワード引数をチェック
        for key, value in kwargs.items():
            if isinstance(value, (int, float)) and value < 0:
                raise ValueError(f"引数 {key} に負の値は許可されていません: {value}")

        return func(*args, **kwargs)

    return wrapper


@ensure_positive
def deposit(balance, amount):
    """残高に入金額を足して返す関数"""
    return balance + amount


print(deposit(100, 50))
# print(deposit(100, -10))  # これを有効にするとエラーが発生する
実行結果
150

バリデーションをデコレータとして切り出すことで、同じロジックを複数の関数で再利用でき、コードの重複を防げます

また、エラーハンドリングのポリシーを一元管理しやすくなる利点もあります。

キャッシュ(メモ化)デコレータの基本

同じ入力に対して何度も重い計算を行う場合、結果をキャッシュしておくと高速化できます。

Pythonにはfunctools.lru_cacheという標準デコレータがありますが、ここでは基本原理を理解するため、簡単なメモ化デコレータを自作してみます。

Python
import functools


def simple_cache(func):
    """引数に対する戻り値をキャッシュする簡易デコレータ"""

    cache = {}

    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            print(f"[CACHE HIT] args={args}")
            return cache[args]

        print(f"[CACHE MISS] args={args}")
        result = func(*args)
        cache[args] = result
        return result

    return wrapper


@simple_cache
def slow_add(a, b):
    """時間のかかる加算処理を模した関数"""
    import time
    time.sleep(1)  # わざと遅くする
    return a + b


print(slow_add(2, 3))  # 初回は遅い
print(slow_add(2, 3))  # 2回目はキャッシュから即返る
実行結果
[CACHE MISS] args=(2, 3)
5
[CACHE HIT] args=(2, 3)
5

実際の開発では、より高機能な@functools.lru_cache(maxsize=128)を使うことが多いですが、デコレータで「引数をキーにした辞書キャッシュ」を管理できるというパターンは覚えておくと便利です。

認証・認可チェック用デコレータの作り方

Webアプリケーションでは、「この処理はログインユーザーだけ」「管理者権限が必要」といった認証・認可のチェックが頻繁に出てきます。

これもデコレータにすることで、各ハンドラの本体をシンプルに保てます。

ここでは、非常に簡略化した例を示します。

Python
import functools


# 疑似的な「現在のユーザー情報」
class CurrentUser:
    def __init__(self, name, is_authenticated, role):
        self.name = name
        self.is_authenticated = is_authenticated
        self.role = role


current_user = CurrentUser("taro", is_authenticated=True, role="admin")


def login_required(func):
    """ログインしているユーザーだけが実行できるようにするデコレータ"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not current_user.is_authenticated:
            raise PermissionError("ログインが必要です")
        return func(*args, **kwargs)

    return wrapper


def require_role(role):
    """特定の権限(ロール)を要求するデコレータ"""

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if current_user.role != role:
                raise PermissionError(f"{role} 権限が必要です")
            return func(*args, **kwargs)

        return wrapper

    return decorator


@login_required
@require_role("admin")
def delete_user(user_id):
    """ユーザーを削除する管理者専用処理"""
    print(f"ユーザー {user_id} を削除しました")


delete_user(123)
実行結果
ユーザー 123 を削除しました

このように、認証・認可の責務をデコレータとして外出しすることで、本来のビジネスロジックを読みやすくできるというメリットがあります。

実際のWebフレームワーク(DjangoやFlaskなど)でも、このパターンが多用されています。

クラスデコレータでクラス定義を拡張する

これまで紹介してきたのは「関数デコレータ」でしたが、デコレータはクラスにも適用できます

クラスデコレータは、クラスを受け取って、修正または拡張されたクラスを返します。

Python
def add_repr(cls):
    """__repr__ メソッドを自動実装するクラスデコレータ"""

    def __repr__(self):
        attrs = ", ".join(
            f"{name}={value!r}"
            for name, value in self.__dict__.items()
        )
        return f"{cls.__name__}({attrs})"

    # クラスに __repr__ メソッドを追加する
    setattr(cls, "__repr__", __repr__)
    return cls


@add_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


p = Point(1, 2)
print(p)  # __repr__ が自動で付与されている
実行結果
Point(x=1, y=2)

このように、クラスデコレータを使うと、クラス定義を後から横断的に拡張できるようになります。

データクラスのような仕組みも、内部的にはクラスデコレータやメタクラスのアイデアに近いものを用いています。

まとめ

Pythonのデコレータは、「関数(やクラス)を受け取り、処理を挟んだ新しい関数(やクラス)を返す」高階関数として理解すると、とても見通しが良くなります。

@記法は単なるシンタックスシュガーであり、その本質は「定義直後の関数(クラス)を別の関数でラップする」ことにあります。

基本構造(引数なし・引数あり・複数適用)を押さえ、標準デコレータ(@staticmethod, @classmethod, @property, @functools.wrapsなど)の挙動を理解すれば、ログ、バリデーション、キャッシュ、認証、クラス拡張といった実践的なパターンにも自然と応用できます。

まずは小さなデコレータから実際に書いてみて、自分のプロジェクトでの共通処理を整理していくところから始めてみてください。

コーディングテクニック

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!