Pythonデコレータの使い方:@propertyや@staticmethodを徹底解説
Pythonのデコレータについて、基本的な使い方から@propertyや@staticmethodなどの組み込みデコレータ、独自デコレータの作成方法まで実例付きで解説。メタプログラミングの第一歩を学ぼう。
Pythonデコレータとは?
デコレータは、関数やクラスの動作を拡張・変更するためのPythonの機能です。関数を引数に取り、新しい関数を返す「高階関数」の一種で、既存のコードを書き換えずに機能を追加できます。@ 記号を使って簡潔に記述できるため、コードの可読性が向上します。
デコレータの基本構文
<h1>デコレータ関数の定義</h1>
def my_decorator(func):
def wrapper():
print("前処理")
func()
print("後処理")
return wrapper
<h1>デコレータの使用</h1>
@my_decorator
def hello():
print("Hello!")
hello()
<h1>出力:</h1>
<h1>前処理</h1>
<h1>Hello!</h1>
<h1>後処理</h1>
@my_decorator は hello = my_decorator(hello) と同じ意味です。
組み込みデコレータ:@property
@property は、メソッドを属性のようにアクセスできるようにするデコレータです。ゲッター、セッター、デリーターを定義できます。
基本的な使い方
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
@radius.deleter
def radius(self):
print("半径を削除します")
del self._radius
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
c.radius = 10
print(c.area) # 314.159
<h1>del c.radius # 半径を削除します</h1>
@property でゲッター、@radius.setter でセッター、@radius.deleter でデリーターを定義。組み込みデコレータ:@staticmethod
@staticmethod は、インスタンスやクラスに依存しないメソッドを定義します。通常の関数と同じですが、クラス内に定義することで名前空間を整理できます。
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def multiply(a, b):
return a * b
print(MathUtils.add(3, 4)) # 7
print(MathUtils.multiply(3, 4)) # 12
self や cls を取らない。@classmethod との違い
@classmethod は第一引数にクラス自体(cls)を受け取るため、クラス変数にアクセスできます。
class MyClass:
class_var = "クラス変数"
@classmethod
def class_method(cls):
print(f"クラスメソッド: {cls.class_var}")
MyClass.class_method() # クラスメソッド: クラス変数
独自デコレータの作成
引数なしデコレータ
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} の実行時間: {end - start:.4f}秒")
return result
return wrapper
@timer
def long_running_task():
sum(range(1000000))
long_running_task()
引数付きデコレータ
デコレータ自体に引数を渡したい場合は、さらに外側の関数でラップします。
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}!")
greet("Alice")
<h1>Hello Alice!</h1>
<h1>Hello Alice!</h1>
<h1>Hello Alice!</h1>
デコレータとメタプログラミング
デコレータは、関数やクラスの振る舞いを動的に変更する「メタプログラミング」の一手法です。以下のような応用が可能です。
クラスデコレータ
クラスにもデコレータを適用できます。
def add_repr(cls):
def __repr__(self):
return f"{cls.__name__}({self.__dict__})"
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) # Point({'x': 1, 'y': 2})
関数の属性を利用したデコレータ
functools.wraps を使うと、ラッパー関数に元の関数の属性(__name__、__doc__ など)をコピーできます。
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""ラッパー関数"""
print("前処理")
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""元の関数"""
pass
print(example.__name__) # example(@wrapsがないとwrapper)
print(example.__doc__) # 元の関数
実践的なデコレータの例
アクセス制御
def require_admin(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_admin"):
raise PermissionError("管理者権限が必要です")
return func(user, *args, **kwargs)
return wrapper
@require_admin
def delete_user(admin, user_id):
print(f"ユーザー {user_id} を削除")
キャッシュ(メモ化)
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
まとめ
@ 構文で関数やクラスに機能を追加する。@property で属性アクセスを制御、@staticmethod で静的メソッドを定義。functools.wraps でデコレートされた関数のメタデータを保持。デコレータをマスターすれば、Pythonプログラミングの幅が大きく広がります。ぜひ実際のコードで試してみてください。