【Python】Decoratorにはどんなものがあるのか?

この記事は約8分で読めます。

デコレータ(Decorator)は、Pythonを読み始めたときにつまずきやすいポイントですが、「JavaのAOP(アスペクト指向)」「WebフレームワークのInterceptor/Filter」のイメージをお持ちであれば、まさにそれです。

開発をしていてしばしば「共通処理をビジネスロジックから切り離したい(関心の分離)」という欲求がありますが、デコレータはそのための仕組みです。

機能追加系のデコレータ

ドキュメント生成ツールやWeb開発でよく見る、3つの具体的な「あるある」パターンを紹介します。


1. 「時間計測」のデコレータ

ドキュメント生成は「重い処理」になりがちです。「PDF生成に何秒かかったか知りたい」という場合、関数の前後に時間を測るコードを書くと、コードが汚れますよね。

デコレータがない場合(コードが汚れる)

Python
import time

def create_heavy_pdf():
    start = time.time()  # 計測開始(本質じゃないコード)
    
    # ... 重いPDF生成処理 ...
    print("PDFを作っています...")
    
    end = time.time()    # 計測終了(本質じゃないコード)
    print(f"処理時間: {end - start}秒")

create_heavy_pdf()

デコレータがある場合(スッキリ!)

Python
# これがデコレータ(共通部品として定義されている想定)
# 「関数の実行時間を測る」という機能を持ったラッパー
@measure_time
def create_heavy_pdf():
    # ... 重いPDF生成処理 ...
    print("PDFを作っています...")

# 使う側は @measure_time をつけるだけ!
# 実行すると自動的に時間がログに出力されます。
create_heavy_pdf()

解説: ソースコードを読む際、@measure_time のような記述を見つけたら、「あ、この関数は実行時間をログに残すんだな」と理解して、中身のロジックに集中すればOKです。


2. 「認証・権限チェック」のデコレータ

「管理者(Admin)しか実行できない処理」を作るときによく使われます。Javaの Filter に近いです。

Python
# ユーザーが管理者かどうかチェックするデコレータ
@login_required
@admin_only
def delete_all_documents():
    # ここには「削除ロジック」だけを書く。
    # 権限チェックのif文はデコレータが肩代わりしてくれる。
    print("全データを削除しました")

解説: 関数が実行されるに、デコレータが割り込んで「ログインしてる?」「管理者?」をチェックします。もし条件を満たしていなければ、delete_all_documents 本体は実行されずにエラーが返されます。


3. 「リトライ(再試行)」のデコレータ

ドキュメント生成では、外部のAPIからデータを取ってくることがあります。ネットワークが一瞬切れただけでエラーにして良いでしょうか? 数回はリトライしたいですよね。

Python
# 失敗しても、最大3回まで1秒待ってやり直してくれる
@retry(max_tries=3, delay=1)
def fetch_data_from_api():
    # 外部APIへの接続処理
    # ネットワークエラーが起きても、デコレータが勝手に再実行してくれる
    pass

解説: これを自力で while 文と try-except で書くと、可読性が一気に下がります。デコレータのおかげで、「データ取得」という本来の目的が明確になります。


番外編:最近よく見る @dataclass

厳密には「処理を挟む」というより「クラスを改造する」デコレータですが、最近のPythonプロジェクトでは必須知識です。

Java/C#のDTO(Data Transfer Object) を作るイメージです。

Python
from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str

# これだけで、コンストラクタ(__init__)や、
# 文字列化メソッド(__str__)、比較メソッド(__eq__)などが
# 自動生成されます(Lombokに近いイメージ)。
user = User(id=1, name="Taro", email="test@example.com")
print(user) 
# 出力: User(id=1, name='Taro', email='test@example.com')

読み方のコツ

ソースコードを読んでいて @ が出てきたら、以下の手順で解読してください。

  1. 名前を見る: @transaction(DBトランザクション管理かな?)、@cache(結果をキャッシュするのかな?)など、名前で推測できることが多いです。

  2. 無視して本体を読む: デコレータはあくまで「付加機能」です。まずは関数本体(ビジネスロジック)が何をしているかを理解してください。

  3. 気になったら定義元へジャンプ: 「このデコレータ、具体的に何をしてるんだろう?」と思ったら、定義元(F12キーなど)に飛んでみてください。中に def wrapper(...): という関数があり、そこで func() を呼び出している構造が見えるはずです。

振る舞い変更系(設定系)の組み込みデコレータ

先ほど説明した「ログ出力」や「認証」のような「機能追加系」のデコレータとは少し毛色が違い、@staticmethod「振る舞い変更系(設定系)」の組み込みデコレータです。

@staticmethod は、Java/C# の static メソッドと同じです。

なぜわざわざ @ を付けて宣言する必要があるのか、Pythonの仕組みと合わせて解説します。


1. なぜ @staticmethod が必要なのか?

Pythonのクラス内の関数(メソッド)は、デフォルトで「インスタンス・メソッド」として扱われます。 これは、「第一引数に必ずインスタンス自身(self)を受け取る」という決まりがあるからです。

Python
class Calculator:
    def add(self, a, b):  # self が勝手に渡される
        return a + b
    
calc = Calculator()
calc.add(1, 2)  # 内部では Calculator.add(calc, 1, 2) のように呼ばれている

しかし、クラスの中には「インスタンスのデータ(self)を使わない、単なる便利関数」を置きたいときがありますよね? そこで @staticmethod をつけると、Pythonに対して「この関数を呼ぶときは self を渡さないでください(ただの関数として扱ってください)」と指示することができます。

2. コードで比較すると分かりやすい

例えば、ドキュメント生成ツールなどであれば、「ファイルパスの操作」や「日付のフォーマット」などの便利関数でよく使われます。

Python
class DocumentGenerator:
    def __init__(self, output_dir):
        self.output_dir = output_dir  # インスタンス変数

    # 【1】通常のメソッド (Instance Method)
    # self を使う。インスタンスの状態にアクセスする。
    def save(self, filename):
        # self.output_dir にアクセスしている
        full_path = f"{self.output_dir}/{filename}"
        print(f"保存しました: {full_path}")

    # 【2】スタティックメソッド (@staticmethod)
    # self を受け取らない! インスタンスの状態には興味がない。
    # 単に「このクラスに関連する便利機能」としてここに置いてあるだけ。
    @staticmethod
    def check_extension(filename):
        # self は使えない
        return filename.endswith(".pdf")

# 使い方
gen = DocumentGenerator("/tmp")

# 通常メソッド呼び出し
gen.save("report.pdf") 

# スタティックメソッド呼び出し
# インスタンスを作らなくても、クラスから直接呼べる(Javaのstaticと同じ)
is_pdf = DocumentGenerator.check_extension("report.pdf")

3. いつ使うべき?

コードレビューの際、以下のような基準で見ると良いです。

  1. 「この関数内で self を一度も使っていないな」と気づいたら、@staticmethod にすることを検討します。

  2. 目的: 「この関数は、クラスの状態(メンバ変数)を書き換えたり読み込んだりしませんよ(=副作用が少ないですよ)」ということを読み手に明示するためです。

補足:もうひとつの兄弟 @classmethod

似たものに @classmethod があります。これもよく出てきます。

  • @staticmethod: 引数に何も特別なものを受け取らない。(ただの関数)

  • @classmethod: 第一引数に クラス自身 (cls) を受け取る。

これは「ファクトリーメソッド(インスタンスを生成するメソッド)」を作るときによく使われます。

タイトルとURLをコピーしました