2021年6月7日月曜日

Python をデコレータをクラスとして定義する方法 (Flask で使う方法もあり)

Python をデコレータをクラスとして定義する方法 (Flask で使う方法もあり)

概要

過去に 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 件のコメント:

コメントを投稿