概要
過去に Python デコレータの使い方を紹介しました 今回はデコレータをクラスとして定義する方法を紹介します
環境
- macOS 11.4
- Python 3.8.7
デコレータ定義
クラスとしてデコレータを定義する際にポイントは __call__
メソッドを実装する点です
また引数付きのデコレータは更に内部関数を呼び出します
参考のためにクラスではなくデコレータを関数として定義する方法も紹介しています
# func の前と後に処理を書く func はデコレータを付与した関数
def first_deco(func):
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
print(" world!")
return ret
return wrapper
def second_deco(name):
def _first_deco(func):
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
print(" world!" + name)
return ret
return wrapper
return _first_deco
# func がデコレータを付与した関数
class FirstDeco():
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
try:
ret = self._func(*args, **kwargs)
print(" world!")
except:
raise
return ret
class SecondDeco():
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, func):
def wrapper_f(*args, **kwargs):
try:
ret = func(*args, **kwargs)
print(" world!" + self._kwargs['name'])
except:
raise
return ret
return wrapper_f
動作確認
- vim test_deco.py
from deco import first_deco, second_deco
from deco import FirstDeco
from deco import SecondDeco as sd
@first_deco
def first(msg):
print(msg, end='')
@second_deco(name="hawk")
def second(msg):
print(msg, end='')
@FirstDeco
def first_with_class(msg):
print(msg, end='')
@sd(name="snowlog")
def second_with_class(msg):
print(msg, end='')
first("hello")
second("hello")
first_with_class("hello")
second_with_class("hello")
- python3 test_deco.py
hello world!
hello world!hawk
hello world!
hello world!snowlog
メリット
クラスとして定義すれば継承などが使えます オブジェクト指向として Python を書きたい人はクラスとして定義したほうがコード的にもキレイになると思います
おまけ: 前のデコレータから値を受け取る方法
class FirstDeco():
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
try:
ret = self._func("from firstdeco !", *args, **kwargs)
except:
raise
return ret
class SecondDeco():
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, func):
def wrapper_f(from_first_deco, *args, **kwargs):
try:
ret = func(from_first_deco, *args, **kwargs)
except:
raise
return ret
return wrapper_f
@FirstDeco
@sd(name="snowlog")
def second_with_class(from_first_deco, msg):
print(from_first_deco)
print(msg, end='')
second_with_class("hello")
おまけ: Flask で使う場合には
引数を取る場合には @functools.wraps(func) を使い引数と取らない場合には functools.update_wrapper を使います
特に引数を取らない場合の書き方はあまり見慣れない書き方かなと思います 更に言うと必ず init の先頭で update_wrapper を呼び出さないと行けない点にも注意が必要です
import functools
class FirstDeco():
def __init__(self, func):
functools.update_wrapper(self, func)
self._func = func
def __call__(self, *args, **kwargs):
try:
ret = self._func(*args, **kwargs)
print(" world!")
except:
raise
return ret
import functools
class SecondDeco():
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, func):
@functools.wraps(func)
def wrapper_f(*args, **kwargs):
try:
ret = func(*args, **kwargs)
print(" world!" + self._kwargs['name'])
except:
raise
return ret
return wrapper_f
0 件のコメント:
コメントを投稿