Python デコレータ入門|関数を装飾する基礎概念

Pythonのデコレータの基本的な使い方から応用例まで解説。関数を装飾して機能を拡張する方法を初心者向けに説明します。

Learning Next 運営
25 分で読めます

Python デコレータ入門|関数を装飾する基礎概念

同じような機能を何度も書いていて、もっと効率的にできないかと思いませんか?

みなさん、Pythonで関数を書いているとき、こんな悩みを抱えていませんか?

「関数の実行時間を測定したい」 「ログを出力する機能を追加したい」 「同じような処理を何度も書いている」

実は、デコレータを使うことで、既存の関数に新しい機能を簡単に追加できるんです。

この記事では、Pythonのデコレータの基本的な使い方から実践的な応用例まで、初心者にも分かりやすく解説します。 具体的なコード例とともに、段階的に理解できるよう丁寧に説明しますね!

デコレータって何?基本概念から理解しよう

デコレータは、関数やクラスを装飾(修飾)する機能のことです。

簡単に言うと、既存の関数に新しい機能を追加したり、動作を変更したりするための仕組みなんです。

最初の例を見てみよう

デコレータは@記号を使って記述します。 装飾したい関数の上に配置するだけで使えます。

def my_decorator(func):
"""デコレータ関数"""
def wrapper():
print("関数実行前の処理")
func()
print("関数実行後の処理")
return wrapper
@my_decorator
def greet():
"""挨拶する関数"""
print("こんにちは!")
# 関数を実行
greet()

このコードを実行すると、以下のような出力が得られます。

関数実行前の処理 こんにちは! 関数実行後の処理

@my_decoratorを付けることで、greet関数に追加の機能を簡単に付与できました。

デコレータの基本的な流れ:

  1. my_decorator関数が元の関数を受け取る
  2. wrapper関数で処理を包み込む
  3. 実行前後に追加の処理を行う
  4. 最後に元の関数を実行

これにより、元の関数を変更することなく、新しい機能を追加できるんです。

デコレータの仕組みを詳しく見てみよう

関数は第一級オブジェクトなんです

Pythonでは、関数は「第一級オブジェクト」として扱われます。 これは、関数を変数に代入したり、他の関数の引数として渡したりできることを意味します。

def hello():
return "Hello, World!"
# 関数を変数に代入
greeting = hello
print(greeting()) # 出力: Hello, World!
# 関数を引数として渡す
def call_function(func):
return func()
result = call_function(hello)
print(result) # 出力: Hello, World!

この例では、hello関数をgreeting変数に代入しています。 そして、call_functionhello関数を引数として渡しています。

関数が変数のように扱えるからこそ、デコレータが可能になるんです。

@記号の正体を理解しよう

デコレータの@記号は、実際には以下の処理の糖衣構文(シンタックスシュガー)です。

def my_decorator(func):
def wrapper():
print("処理前")
func()
print("処理後")
return wrapper
# @記号を使った場合
@my_decorator
def function1():
print("関数1")
# 上記と同等の処理
def function2():
print("関数2")
function2 = my_decorator(function2)
# 両方とも同じ動作
function1()
function2()

このコードを実行すると、どちらも同じ結果が出力されます。

@my_decoratorは、実際にはfunction1 = my_decorator(function1)と同じ処理を行っているんです。 つまり、元の関数を新しい関数に置き換えているということですね。

引数を持つ関数にデコレータを適用してみよう

*argsと**kwargsで柔軟に対応

実際の関数は引数を持つことが多いため、デコレータも引数に対応する必要があります。

def log_decorator(func):
"""関数の実行をログに記録するデコレータ"""
def wrapper(*args, **kwargs):
print(f"関数 {func.__name__} を実行中...")
print(f"引数: {args}, キーワード引数: {kwargs}")
result = func(*args, **kwargs)
print(f"戻り値: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
"""2つの数を加算する関数"""
return a + b
@log_decorator
def greet(name, greeting="こんにちは"):
"""挨拶を行う関数"""
return f"{greeting}{name}さん!"
# 使用例
result1 = add(5, 3)
result2 = greet("田中", greeting="おはよう")

このコードでは、*args**kwargsを使って任意の引数を受け取れるようにしています。

*argsは位置引数を、**kwargsはキーワード引数をまとめて受け取ります。 これにより、どんな引数の組み合わせでも対応できるんです。

実行すると、以下のような出力が得られます。

関数 add を実行中... 引数: (5, 3), キーワード引数: {} 戻り値: 8 関数 greet を実行中... 引数: ('田中',), キーワード引数: {'greeting': 'おはよう'} 戻り値: おはよう、田中さん!

functools.wrapsで関数情報を保持しよう

デコレータを使うと、元の関数の情報(名前、docstringなど)が失われてしまいます。 functools.wrapsを使うことで、この問題を解決できます。

import functools
def better_decorator(func):
"""改良されたデコレータ"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"実行中: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@better_decorator
def calculate(x, y):
"""数値の計算を行う関数"""
return x * y
# 元の関数の情報が保持される
print(calculate.__name__) # 出力: calculate
print(calculate.__doc__) # 出力: 数値の計算を行う関数

@functools.wraps(func)をつけることで、元の関数の__name____doc__が保持されます。

これにより、デコレータを使っても、元の関数の情報を確認できるようになります。

実際に役立つデコレータを作ってみよう

実行時間測定デコレータ

関数の実行時間を測定するデコレータを作ってみましょう。

import time
import functools
def timing_decorator(func):
"""関数の実行時間を測定するデコレータ"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"{func.__name__} の実行時間: {execution_time:.4f}秒")
return result
return wrapper
@timing_decorator
def slow_function():
"""時間のかかる処理をシミュレート"""
time.sleep(1)
return "処理完了"
@timing_decorator
def calculate_sum(n):
"""1からnまでの合計を計算"""
return sum(range(1, n + 1))
# 使用例
result1 = slow_function()
result2 = calculate_sum(1000000)

このデコレータは、関数の実行前後で時間を測定しています。

time.time()で現在時刻を取得し、実行前後の時間の差を計算しています。 これにより、関数の処理時間を簡単に測定できるんです。

キャッシュデコレータ

関数の結果をキャッシュして、同じ引数での再実行を高速化するデコレータです。

import functools
def cache_decorator(func):
"""関数の結果をキャッシュするデコレータ"""
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# キャッシュのキーを作成
key = str(args) + str(sorted(kwargs.items()))
if key in cache:
print(f"キャッシュから結果を取得: {func.__name__}")
return cache[key]
print(f"計算を実行: {func.__name__}")
result = func(*args, **kwargs)
cache[key] = result
return result
return wrapper
@cache_decorator
def fibonacci(n):
"""フィボナッチ数列を計算(再帰版)"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 使用例
print(fibonacci(10)) # 初回は計算
print(fibonacci(10)) # 2回目はキャッシュから取得

このデコレータは、引数をキーとして結果を辞書に保存します。

同じ引数で再度呼び出されたときは、保存された結果を返すため、計算時間を大幅に短縮できます。 フィボナッチ数列のような再帰的な計算で特に効果的です。

認証デコレータ

アクセス制御を行うデコレータの例です。

import functools
def require_auth(func):
"""認証が必要な関数用のデコレータ"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 簡単な認証チェック(実際にはより複雑な処理)
if not hasattr(wrapper, 'authenticated'):
print("認証が必要です。ログインしてください。")
return None
print("認証済み: 関数を実行します")
return func(*args, **kwargs)
return wrapper
@require_auth
def access_sensitive_data():
"""機密データにアクセスする関数"""
return "機密情報: 重要なデータ"
# 認証前
result1 = access_sensitive_data() # 認証エラー
# 認証後
access_sensitive_data.authenticated = True
result2 = access_sensitive_data() # 実行成功

このデコレータは、関数の実行前に認証状態をチェックします。

認証されていない場合はエラーメッセージを表示し、認証されている場合のみ関数を実行します。 実際のアプリケーションでは、より複雑な認証ロジックを実装することになります。

パラメータ付きデコレータを使ってみよう

デコレータファクトリーの作成

デコレータ自体にパラメータを渡すことも可能です。

import functools
def repeat(times):
"""指定回数だけ関数を実行するデコレータファクトリー"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for i in range(times):
print(f"実行回数: {i + 1}")
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
@repeat(3)
def greet(name):
"""挨拶を行う関数"""
return f"こんにちは、{name}さん!"
# 使用例
results = greet("田中")
print(results)

このコードでは、repeat(3)でデコレータファクトリーを呼び出しています。

repeat関数はdecorator関数を返し、そのdecorator関数が実際のデコレータとして動作します。 これにより、パラメータを受け取るデコレータを作成できるんです。

条件付きデコレータ

時間帯に応じて実行を制御するデコレータです。

import functools
from datetime import datetime
def only_during_hours(start_hour, end_hour):
"""指定時間帯のみ実行可能なデコレータ"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
current_hour = datetime.now().hour
if start_hour <= current_hour < end_hour:
return func(*args, **kwargs)
else:
print(f"この機能は{start_hour}時から{end_hour}時の間のみ利用可能です")
return None
return wrapper
return decorator
@only_during_hours(9, 17)
def business_function():
"""営業時間中のみ実行可能な関数"""
return "業務処理を実行中..."
# 使用例
result = business_function()

このデコレータは、現在時刻をチェックして、指定された時間帯のみ関数を実行します。

営業時間外の場合は、エラーメッセージを表示して処理を停止します。 実際のシステムでは、メンテナンス時間の制御などに活用できます。

複数のデコレータを組み合わせよう

デコレータの重ね掛け

複数のデコレータを一つの関数に適用することができます。

import functools
import time
def timing_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"実行時間: {end - start:.4f}秒")
return result
return wrapper
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"実行開始: {func.__name__}")
result = func(*args, **kwargs)
print(f"実行完了: {func.__name__}")
return result
return wrapper
@timing_decorator
@log_decorator
def complex_calculation(n):
"""複雑な計算を行う関数"""
time.sleep(0.1) # 処理時間をシミュレート
return sum(i**2 for i in range(n))
# 使用例
result = complex_calculation(1000)

このコードでは、timing_decoratorlog_decoratorを組み合わせています。

実行すると、以下のような出力が得られます。

実行開始: complex_calculation 実行完了: complex_calculation 実行時間: 0.1001秒

実行順序の理解

デコレータは下から上に適用されます。

# 以下の書き方は
@decorator_a
@decorator_b
def func():
pass
# これと同等です
# func = decorator_a(decorator_b(func))

つまり、decorator_bが先に適用され、その結果にdecorator_aが適用されます。

クラスベースのデコレータを作ってみよう

__call__メソッドを使用

クラスを使ってデコレータを作ることもできます。

import functools
import time
class TimingDecorator:
"""クラスベースのタイミングデコレータ"""
def __init__(self, func):
self.func = func
self.call_count = 0
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
self.call_count += 1
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(f"呼び出し回数: {self.call_count}")
print(f"実行時間: {end_time - start_time:.4f}秒")
return result
@TimingDecorator
def sample_function(n):
"""サンプル関数"""
return sum(range(n))
# 使用例
result1 = sample_function(1000)
result2 = sample_function(2000)

クラスベースのデコレータでは、__call__メソッドを定義することで、インスタンスを関数のように呼び出せます。

また、インスタンス変数を使って状態を保持できるため、呼び出し回数などの情報を管理できます。

パラメータ付きクラスデコレータ

import functools
class RetryDecorator:
"""リトライ機能付きデコレータ"""
def __init__(self, max_retries=3):
self.max_retries = max_retries
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(self.max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == self.max_retries:
print(f"最大リトライ回数に達しました: {e}")
raise
print(f"リトライ {attempt + 1}/{self.max_retries}: {e}")
return wrapper
@RetryDecorator(max_retries=2)
def unreliable_function():
"""失敗する可能性のある関数"""
import random
if random.random() < 0.7:
raise ValueError("処理に失敗しました")
return "成功!"
# 使用例
try:
result = unreliable_function()
print(result)
except ValueError as e:
print(f"最終的に失敗: {e}")

このクラスデコレータは、関数の実行に失敗した場合に指定回数だけリトライします。

__init__メソッドでリトライ回数を設定し、__call__メソッドで実際のデコレータを返します。

組み込みデコレータを活用しよう

property, staticmethod, classmethod

Pythonには、いくつかの組み込みデコレータが用意されています。

class Circle:
"""円を表すクラス"""
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""半径のゲッター"""
return self._radius
@radius.setter
def radius(self, value):
"""半径のセッター"""
if value < 0:
raise ValueError("半径は正の値である必要があります")
self._radius = value
@property
def area(self):
"""面積を計算するプロパティ"""
import math
return math.pi * self._radius ** 2
@staticmethod
def is_valid_radius(radius):
"""有効な半径かどうかをチェック"""
return radius > 0
@classmethod
def from_diameter(cls, diameter):
"""直径から円を作成するクラスメソッド"""
return cls(diameter / 2)
# 使用例
circle = Circle(5)
print(f"半径: {circle.radius}")
print(f"面積: {circle.area}")
# クラスメソッドの使用
circle2 = Circle.from_diameter(10)
print(f"直径10の円の半径: {circle2.radius}")
# スタティックメソッドの使用
print(f"半径5は有効: {Circle.is_valid_radius(5)}")

この例では、3つの組み込みデコレータを使用しています。

@property:メソッドを属性のように扱えるようにします。 @staticmethod:クラスに関連するが、インスタンスを必要としないメソッドです。 @classmethod:クラス自体を第一引数として受け取るメソッドです。

これらのデコレータを使うことで、より柔軟なクラス設計が可能になります。

まとめ

Pythonのデコレータは、関数に機能を追加するための強力な仕組みです。 基本的な使い方から応用例まで、様々な場面で活用できます。

この記事で学んだ内容:

  • デコレータは@記号を使って関数を装飾する
  • 関数の実行前後に処理を追加できる
  • パラメータ付きデコレータで柔軟な制御が可能
  • クラスベースのデコレータも作成できる
  • 組み込みデコレータ(property、staticmethod等)も活用できる

デコレータを使うことで、コードの重複を削減し、関心の分離を実現できます。

最初は難しく感じるかもしれませんが、基本的なパターンを覚えれば簡単に使えるようになります。 ぜひ実際のプロジェクトで活用してみてください!

関連記事