デコレータ(Decorator)は、Pythonを読み始めたときにつまずきやすいポイントですが、「JavaのAOP(アスペクト指向)」や「WebフレームワークのInterceptor/Filter」のイメージをお持ちであれば、まさにそれです。
開発をしていてしばしば「共通処理をビジネスロジックから切り離したい(関心の分離)」という欲求がありますが、デコレータはそのための仕組みです。
機能追加系のデコレータ
ドキュメント生成ツールやWeb開発でよく見る、3つの具体的な「あるある」パターンを紹介します。
1. 「時間計測」のデコレータ
ドキュメント生成は「重い処理」になりがちです。「PDF生成に何秒かかったか知りたい」という場合、関数の前後に時間を測るコードを書くと、コードが汚れますよね。
デコレータがない場合(コードが汚れる)
import time
def create_heavy_pdf():
start = time.time() # 計測開始(本質じゃないコード)
# ... 重いPDF生成処理 ...
print("PDFを作っています...")
end = time.time() # 計測終了(本質じゃないコード)
print(f"処理時間: {end - start}秒")
create_heavy_pdf()
デコレータがある場合(スッキリ!)
# これがデコレータ(共通部品として定義されている想定)
# 「関数の実行時間を測る」という機能を持ったラッパー
@measure_time
def create_heavy_pdf():
# ... 重いPDF生成処理 ...
print("PDFを作っています...")
# 使う側は @measure_time をつけるだけ!
# 実行すると自動的に時間がログに出力されます。
create_heavy_pdf()
解説: ソースコードを読む際、@measure_time のような記述を見つけたら、「あ、この関数は実行時間をログに残すんだな」と理解して、中身のロジックに集中すればOKです。
2. 「認証・権限チェック」のデコレータ
「管理者(Admin)しか実行できない処理」を作るときによく使われます。Javaの Filter に近いです。
# ユーザーが管理者かどうかチェックするデコレータ
@login_required
@admin_only
def delete_all_documents():
# ここには「削除ロジック」だけを書く。
# 権限チェックのif文はデコレータが肩代わりしてくれる。
print("全データを削除しました")
解説: 関数が実行される前に、デコレータが割り込んで「ログインしてる?」「管理者?」をチェックします。もし条件を満たしていなければ、delete_all_documents 本体は実行されずにエラーが返されます。
3. 「リトライ(再試行)」のデコレータ
ドキュメント生成では、外部のAPIからデータを取ってくることがあります。ネットワークが一瞬切れただけでエラーにして良いでしょうか? 数回はリトライしたいですよね。
# 失敗しても、最大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) を作るイメージです。
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')
読み方のコツ
ソースコードを読んでいて @ が出てきたら、以下の手順で解読してください。
-
名前を見る:
@transaction(DBトランザクション管理かな?)、@cache(結果をキャッシュするのかな?)など、名前で推測できることが多いです。 -
無視して本体を読む: デコレータはあくまで「付加機能」です。まずは関数本体(ビジネスロジック)が何をしているかを理解してください。
-
気になったら定義元へジャンプ: 「このデコレータ、具体的に何をしてるんだろう?」と思ったら、定義元(F12キーなど)に飛んでみてください。中に
def wrapper(...):という関数があり、そこでfunc()を呼び出している構造が見えるはずです。
振る舞い変更系(設定系)の組み込みデコレータ
先ほど説明した「ログ出力」や「認証」のような「機能追加系」のデコレータとは少し毛色が違い、@staticmethod は「振る舞い変更系(設定系)」の組み込みデコレータです。
@staticmethod は、Java/C# の static メソッドと同じです。
なぜわざわざ @ を付けて宣言する必要があるのか、Pythonの仕組みと合わせて解説します。
1. なぜ @staticmethod が必要なのか?
Pythonのクラス内の関数(メソッド)は、デフォルトで「インスタンス・メソッド」として扱われます。 これは、「第一引数に必ずインスタンス自身(self)を受け取る」という決まりがあるからです。
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. コードで比較すると分かりやすい
例えば、ドキュメント生成ツールなどであれば、「ファイルパスの操作」や「日付のフォーマット」などの便利関数でよく使われます。
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. いつ使うべき?
コードレビューの際、以下のような基準で見ると良いです。
-
「この関数内で
selfを一度も使っていないな」と気づいたら、@staticmethodにすることを検討します。 -
目的: 「この関数は、クラスの状態(メンバ変数)を書き換えたり読み込んだりしませんよ(=副作用が少ないですよ)」ということを読み手に明示するためです。
補足:もうひとつの兄弟 @classmethod
似たものに @classmethod があります。これもよく出てきます。
-
@staticmethod: 引数に何も特別なものを受け取らない。(ただの関数) -
@classmethod: 第一引数に クラス自身 (cls) を受け取る。
これは「ファクトリーメソッド(インスタンスを生成するメソッド)」を作るときによく使われます。
