LoginSignup
16
18

More than 3 years have passed since last update.

Pythonの関数デコレータをClassで作る

Last updated at Posted at 2020-02-15

前の記事で、デコレータを関数定義としてフレームワーク作成しました。
今回は、それを前提に、Class で実装してみます。(Class デコレータではありません。関数デコレータの Class 定義です)

「デコレータとは」というところから説明が必要な場合は、以前の投稿(「デコレータ(decorator) とは - Python の汎用デコレータ(decorator)フレームワークを作る」) をご覧ください。

class によるデコレータの応用」では、継承を利用した class 型デコレータの応用方法を記載しています。

  1. 引数のないデコレータ Class
  2. 引数のあるデコレータ Class
  3. 引数の有無を統合
  4. 最後の仕上げ
  5. class によるデコレータの応用
    1. 継承したクラス(サブクラス)でデコレータを実装
    2. 親クラス(スーパークラス)のデコレータを継承
  6. まとめ
    1. decorator_class_framework.py

引数のないデコレータ Class

デコレータは呼び出し可能なオブジェクトである必要があります。

@my_decorator
def f(arg):
    pass

というデコレータ表記は

def f(arg):
    pass
f = my_decorator(f)

と等価です。
つまり、my_decorator というクラスを定義する場合、このクラスが呼び出し可能である必要があります。

my_decorator が最初に呼び出された時 ( f = my_decorator(f) ) には __init__() が呼び出されます。
上の例では、__init__(f) が呼び出されます。
そして生成されたインスタンスが f に代入されます。(__init__() の戻り値が代入されるわけではないことに注意してください。)

置き換えられた f が呼び出されると、my_decorator オブジェクトが呼び出されます。クラスオブジェクトを関数として呼び出す場合には、 __call__ を実装しておきます。

sample_001.py
class my_decorator_001:
    def __init__( self, func ):
        print( "called __init__" )
        self._func = func
    def __call__( self, *args, **kwargs ):
        print( "called __call__ with args:", args, "kwargs:", kwargs )
        try:
            ret = self._func( *args, **kwargs )
        except:
            raise
        print( "post func" )
        return ret

@my_decorator_001
def f_001( arg1 ):
    print( "in f_001 with arg1:", arg1 )

f_001( "test 001" )
実行結果 001
>>> @my_decorator_001
... def f_001( arg1 ):
...     print( "in f_001 with arg1:", arg1 )
...
called __init__
>>> f_001( "test 001" )
called __call__ with args: ('test 001',) kwargs: {}
in f_001 with arg1: test 001
post func
>>>

引数のあるデコレータ Class

デコレータに引数を与える場合を考えます。

@my_decorator(arg1, arg2)
def f(arg):
    pass

というデコレータ表記は

def f(arg):
    pass
f = my_decorator(arg1, arg2)(f)

と等価です。my_decorator(arg1, arg2) の部分で、2つの引数とともに __init__ が呼び出され、インスタンスが作成されます。
作成されたインスタンスに対して引数に f が指定されて __call__ が呼び出されます。

sample_002.py
class my_decorator_002:
    def __init__( self, *args, **kwargs ):
        print( "called __init__ with args:", args, "kwargs:", kwargs )
        self._args = args
        self._kwargs = kwargs
    def __call__( self, func ):
        print( "called __call__ with func:", func )
        def wrapper_f( *args, **kwargs ):
            print( "called wrapper_f with args:", args, "kwargs:", kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            print( "post func" )
            return ret
        return wrapper_f

@my_decorator_002( "arg1", "arg2" )
def f_002( arg1 ):
    print( "in f_002 with arg1:", arg1 )

f_002( "test 002" )
実行結果 002
>>> @my_decorator_002( "arg1", "arg2" )
... def f_002( arg1 ):
...     print( "in f_002 with arg1:", arg1 )
...
called __init__ with args: ('arg1', 'arg2') kwargs: {}
called __call__ with func: <function f_002 at 0x76b326a8>
>>> f_002( "test 002" )
called wrapper_f with args: ('test 002',) kwargs: {}
in f_002 with arg1: test 002
post func
>>>

引数の有無を統合

引数を指定してもしなくても動作するように統合します。

引数の有無によって、__init____call__ の引数、__call__ の呼び出しタイミングが異なります。

デコレータの引数→ 引数なし 引数あり(空の引数も含む)
__init__() __init__(func) __init__(args,...)
__call__() 関数呼び出し時 __call__(args,...) デコレート時 __call__(func)

func が呼び出し可能な単一の引数であることを判別に利用します。(そのため、この記事で作成するデコレータの第1引数には、他の呼び出し可能なオブジェクトを指定することはできなくなります。)

@wraps も指定しておきます。
(@wraps については、前の記事の「最後の仕上げ - Python の汎用デコレータ(decorator)フレームワークを作る」をご覧ください。)

sample_003.py
from functools import wraps

class my_decorator_003:
    def __init__( self, *args, **kwargs ):
        print( "called __init__ with args:", args, "kwargs:", kwargs )
        self._func = None
        self._args = []
        self._kwargs = {}
        if len(args) == 1 and callable(args[0]):
            self._func = self._my_decorator( args[0] )
        else:
            self._args = args
            self._kwargs = kwargs
    def __call__( self, *args, **kwargs ):
        print( "called __call__ with args:", args, "kwargs:", kwargs )
        if self._func is None:
            if len(args) == 1 and callable(args[0]):
                self._func = self._my_decorator( args[0] )
                return self._func
        else:
            try:
                ret = self._func( *args, **kwargs )
            except:
                raise
            return ret
    def _my_decorator( self, func ):
        print( "called _my_decorator with func:", func )
        @wraps(func)
        def wrapper_f( *args, **kwargs ):
            print( "called wrapper_f with",
                "args:", args, "kwargs:", kwargs,
                "priv args:", self._args, "kwargs:", self._kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            print( "post func" )
            return ret
        return wrapper_f

def f_003_0( arg1 ):
    """Doc Test"""
    print( "in f_003_0 with arg1:", arg1 )

@my_decorator_003
def f_003_1( arg1 ):
    """Doc Test"""
    print( "in f_003_1 with arg1:", arg1 )

@my_decorator_003( "arg1", arg2="arg2_str" )
def f_003_2( arg1 ):
    """Doc Test"""
    print( "in f_003_2 with arg1:", arg1 )

@my_decorator_003()
def f_003_3( arg1 ):
    """Doc Test"""
    print( "in f_003_3 with arg1:", arg1 )

f_003_0( "test 003" )
f_003_1( "test 003" )
f_003_2( "test 003" )
f_003_3( "test 003" )
実行結果 003
>>> def f_003_0( arg1 ):
...     """Doc Test"""
...     print( "in f_003_0 with arg1:", arg1 )
...
>>> @my_decorator_003
... def f_003_1( arg1 ):
...     """Doc Test"""
...     print( "in f_003_1 with arg1:", arg1 )
...
called __init__ with args: (<function f_003_1 at 0x7697b540>,) kwargs: {}
called _my_decorator with func: <function f_003_1 at 0x7697b540>
>>> @my_decorator_003( "arg1", arg2="arg2_str" )
... def f_003_2( arg1 ):
...     """Doc Test"""
...     print( "in f_003_2 with arg1:", arg1 )
...
called __init__ with args: ('arg1',) kwargs: {'arg2': 'arg2_str'}
called __call__ with args: (<function f_003_2 at 0x7697b5d0>,) kwargs: {}
called _my_decorator with func: <function f_003_2 at 0x7697b5d0>
>>> @my_decorator_003()
... def f_003_3( arg1 ):
...     """Doc Test"""
...     print( "in f_003_3 with arg1:", arg1 )
...
called __init__ with args: () kwargs: {}
called __call__ with args: (<function f_003_3 at 0x7697b6f0>,) kwargs: {}
called _my_decorator with func: <function f_003_3 at 0x7697b6f0>
>>> f_003_0( "test 003" )
in f_003_0 with arg1: test 003
>>> f_003_1( "test 003" )
called __call__ with args: ('test 003',) kwargs: {}
called wrapper_f with args: ('test 003',) kwargs: {} priv args: [] kwargs: {}
in f_003_1 with arg1: test 003
post func
>>> f_003_2( "test 003" )
called wrapper_f with args: ('test 003',) kwargs: {} priv args: ('arg1',) kwargs: {'arg2': 'arg2_str'}
in f_003_2 with arg1: test 003
post func
>>> f_003_3( "test 003" )
called wrapper_f with args: ('test 003',) kwargs: {} priv args: () kwargs: {}
in f_003_3 with arg1: test 003
post func
>>>

最後の仕上げ

sample_003.py でほぼ完成なのですが、Class 型デコレータでは、もう一つ注意が必要です。
sample_003.py を使って、こんな関数をデコレートしてみます。

sample_003_2.py
@my_decorator_003
def f_003_4():
    print( "called:", f_003_4.__name__ )

これを実行すると……

実行結果
>>> @my_decorator_003
... def f_003_4():
...     print( "called:", f_003_4.__name__ )
...
called __init__ with args: (<function f_003_4 at 0x76927618>,) kwargs: {}
called _my_decorator with func: <function f_003_4 at 0x76927618>
>>> f_003_4()
called __call__ with args: () kwargs: {}
called wrapper_f with args: () kwargs: {} priv args: [] kwargs: {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 20, in __call__
  File "<stdin>", line 32, in wrapper_f
  File "<stdin>", line 3, in f_003_4
AttributeError: 'my_decorator_003' object has no attribute '__name__'
>>> def f_003_5():
...     print( "called:", f_003_5.__name__ )
...
>>> f_003_5()
called: f_003_5
>>>

AttributeError: 'my_decorator_003' object has no attribute '__name__' と表示され、__name__ という属性が無いというエラーになってしまいました。もちろん、デコレータなしで定義、参照した場合に表示されるのは、上の実行結果のとおりです。

クラスには __name__ 属性がなく、@wrap で保存しようとしても、自動では作成してくれません。
明示的に追加しておく必要があります。

また、__init__ 内で _my_decorator を呼び出すと、この段階では __doc__ がありませんので、これも明示的に追加しておきます。

sample_004.py
from functools import wraps

class my_decorator_004:
    def __init__( self, *args, **kwargs ):
        print( "called __init__ with args:", args, "kwargs:", kwargs )
        self._func = None
        self._args = []
        self._kwargs = {}
        if len(args) == 1 and callable(args[0]):
            self._func = self._my_decorator( args[0] )
        else:
            self._args = args
            self._kwargs = kwargs
    def __call__( self, *args, **kwargs ):
        print( "called __call__ with args:", args, "kwargs:", kwargs )
        if self._func is None:
            if len(args) == 1 and callable(args[0]):
                self._func = self._my_decorator( args[0] )
                return self._func
        else:
            try:
                ret = self._func( *args, **kwargs )
            except:
                raise
            return ret
    def _my_decorator( self, func ):
        print( "called _my_decorator with func:", func )
        @wraps(func)
        def wrapper_f( *args, **kwargs ):
            print( "called wrapper_f with",
                "args:", args, "kwargs:", kwargs,
                "priv args:", self._args, "kwargs:", self._kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            print( "post func" )
            return ret
        # wrapper_f 内で、__name__ と __doc__ を操作する場合には
        # 下の setattr は削除する
        setattr( self, "__name__", wrapper_f.__name__ )
        setattr( self, "__doc__", wrapper_f.__doc__ )
        return wrapper_f

def f_004_0( arg1 ):
    """Doc Test 0"""
    print( "in f_004_0 with arg1:", arg1 )
    print( "__name__:", f_004_0.__name__ )
    print( "__doc__:", f_004_0.__doc__ )

@my_decorator_004
def f_004_1( arg1 ):
    """Doc Test 1"""
    print( "in f_004_1 with arg1:", arg1 )
    print( "__name__:", f_004_1.__name__ )
    print( "__doc__:", f_004_1.__doc__ )

@my_decorator_004( "arg1", arg2="arg2_str" )
def f_004_2( arg1 ):
    """Doc Test 2"""
    print( "in f_004_2 with arg1:", arg1 )
    print( "__name__:", f_004_2.__name__ )
    print( "__doc__:", f_004_2.__doc__ )

@my_decorator_004()
def f_004_3( arg1 ):
    """Doc Test 3"""
    print( "in f_004_3 with arg1:", arg1 )
    print( "__name__:", f_004_3.__name__ )
    print( "__doc__:", f_004_3.__doc__ )

f_004_0( "test 004" )
f_004_1( "test 004" )
f_004_2( "test 004" )
f_004_3( "test 004" )
実行結果
>>> def f_004_0( arg1 ):
...     """Doc Test 0"""
...     print( "in f_004_0 with arg1:", arg1 )
...     print( "__name__:", f_004_0.__name__ )
...     print( "__doc__:", f_004_0.__doc__ )
...
>>> @my_decorator_004
... def f_004_1( arg1 ):
...     """Doc Test 1"""
...     print( "in f_004_1 with arg1:", arg1 )
...     print( "__name__:", f_004_1.__name__ )
...     print( "__doc__:", f_004_1.__doc__ )
...
called __init__ with args: (<function f_004_1 at 0x768fd420>,) kwargs: {}
called _my_decorator with func: <function f_004_1 at 0x768fd420>
>>> @my_decorator_004( "arg1", arg2="arg2_str" )
... def f_004_2( arg1 ):
...     """Doc Test 2"""
...     print( "in f_004_2 with arg1:", arg1 )
...     print( "__name__:", f_004_2.__name__ )
...     print( "__doc__:", f_004_2.__doc__ )
...
called __init__ with args: ('arg1',) kwargs: {'arg2': 'arg2_str'}
called __call__ with args: (<function f_004_2 at 0x768fd540>,) kwargs: {}
called _my_decorator with func: <function f_004_2 at 0x768fd540>
>>> @my_decorator_004()
... def f_004_3( arg1 ):
...     """Doc Test 3"""
...     print( "in f_004_3 with arg1:", arg1 )
...     print( "__name__:", f_004_3.__name__ )
...     print( "__doc__:", f_004_3.__doc__ )
...
called __init__ with args: () kwargs: {}
called __call__ with args: (<function f_004_3 at 0x768fd618>,) kwargs: {}
called _my_decorator with func: <function f_004_3 at 0x768fd618>
>>> f_004_0( "test 004" )
in f_004_0 with arg1: test 004
__name__: f_004_0
__doc__: Doc Test 0
>>> f_004_1( "test 004" )
called __call__ with args: ('test 004',) kwargs: {}
called wrapper_f with args: ('test 004',) kwargs: {} priv args: [] kwargs: {}
in f_004_1 with arg1: test 004
__name__: f_004_1
__doc__: Doc Test 1
post func
>>> f_004_2( "test 004" )
called wrapper_f with args: ('test 004',) kwargs: {} priv args: ('arg1',) kwargs: {'arg2': 'arg2_str'}
in f_004_2 with arg1: test 004
__name__: f_004_2
__doc__: Doc Test 2
post func
>>> f_004_3( "test 004" )
called wrapper_f with args: ('test 004',) kwargs: {} priv args: () kwargs: {}
in f_004_3 with arg1: test 004
__name__: f_004_3
__doc__: Doc Test 3
post func
>>>

ただし、コメントにも書きましたが、wrapper_f() 内で __name____doc__ を操作する必要があるばあいには、setattr() は行わずに、独自に管理することが必要です。

class によるデコレータの応用

デコレータを関数ではなく、クラスで実装することで、次のような応用が可能です。

継承したクラス(サブクラス)でデコレータを実装

sample_005.py
from functools import wraps

class My_Base:
    def __init__( self ):
        self._my_string = "This is Base Class"

    def getstr( self ):
        return self._my_string

    def putstr( self, string="" ):
        self._my_string = string


class my_decorator_005(My_Base):
    def __init__( self, *args, **kwargs ):
        super().__init__()

        self._func = None
        self._args = []
        self._kwargs = {}
        if len(args) == 1 and callable(args[0]):
            self._func = self._my_decorator( args[0] )
        else:
            self._args = args
            self._kwargs = kwargs

    def __call__( self, *args, **kwargs ):
        if self._func is None:
            if len(args) == 1 and callable(args[0]):
                self._func = self._my_decorator( args[0] )
                return self._func
        else:
            try:
                ret = self._func( *args, **kwargs )
            except:
                raise
            return ret

    def _my_decorator( self, func ):
        @wraps(func)
        def wrapper_f( *args, **kwargs ):
            print( "called wrapper_f with",
                "args:", args, "kwargs:", kwargs,
                "priv args:", self._args, "kwargs:", self._kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            return ret
        # wrapper_f 内で、__name__ と __doc__ を操作する場合には
        # 下の setattr は削除する
        setattr( self, "__name__", wrapper_f.__name__ )
        setattr( self, "__doc__", wrapper_f.__doc__ )
        return wrapper_f


@my_decorator_005
def f_005(arg):
    print( arg )


f_005( 'test 005' )
print( f_005.getstr() )
f_005.putstr( "new string" )
print( f_005.getstr() )
実行結果
$ python sample_005.py
called wrapper_f with args: ('test 005',) kwargs: {} priv args: [] kwargs: {}
test 005
This is Base Class
new string
$

sample_005.py は、スーパークラスを継承してデコレータを作成した例で、対象となった関数(実体はクラスインスタンス)からスーパークラスのメソッドを呼び出すことができます。
(関数として定義したはずなのに、クラスインスタンスになっているという気持ち悪さはありますが、デコレータをクラスで実装すると、関数名は常にクラスインスタンスを指すものになります)

親クラス(スーパークラス)のデコレータを継承

次の例 sample_006.py は、逆に既存のデコレータを継承して、新たなデコレータを作成する例です。
サブクラスで _my_decorator() を実装しています。

sample_006.py
from functools import wraps

class my_decorator_006:
    def __init__( self, *args, **kwargs ):
        self._func = None
        self._args = []
        self._kwargs = {}
        if len(args) == 1 and callable(args[0]):
            self._func = self._my_decorator( args[0] )
        else:
            self._args = args
            self._kwargs = kwargs
            print( "args and kwargs:", self._args, self._kwargs )

    def __call__( self, *args, **kwargs ):
        if self._func is None:
            if len(args) == 1 and callable(args[0]):
                self._func = self._my_decorator( args[0] )
                return self._func
        else:
            try:
                ret = self._func( *args, **kwargs )
            except:
                raise
            return ret

    def _my_decorator( self, func ):
        # _my_decorator() はサブクラスで実装する
        return func


# my_decorator_006 を継承して、新しいデコレータクラスを定義
class new_my_decorator(my_decorator_006):
    def __init__( self, *args, **kwargs ):
        super().__init__( *args, **kwargs )

    # スーパークラスの _my_decorator() をオーバライド
    def _my_decorator( self, func ):
        @wraps(func)
        def wrapper_f( *args, **kwargs ):
            print( "called wrapper_f with",
                "args:", args, "kwargs:", kwargs,
                "priv args:", self._args, "kwargs:", self._kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            return ret
        # wrapper_f 内で、__name__ と __doc__ を操作する場合には
        # 下の setattr は削除する
        setattr( self, "__name__", wrapper_f.__name__ )
        setattr( self, "__doc__", wrapper_f.__doc__ )
        return wrapper_f

@new_my_decorator
def f_006_1(arg):
    print( arg )

@new_my_decorator()
def f_006_2(arg):
    print( arg )

@new_my_decorator( "new_my_decorator with arg")
def f_006_3(arg):
    print( arg )


print( "calling f_006s" )
f_006_1( 'test f_006_1' )
f_006_2( 'test f_006_2' )
f_006_3( 'test f_006_3' )
実行結果
$ python sample_006.py
args and kwargs: () {}
args and kwargs: ('new_my_decorator with arg',) {}
calling f_006s
called wrapper_f with args: ('test f_006_1',) kwargs: {} priv args: [] kwargs: {}
test f_006_1
called wrapper_f with args: ('test f_006_2',) kwargs: {} priv args: () kwargs: {}
test f_006_2
called wrapper_f with args: ('test f_006_3',) kwargs: {} priv args: ('new_my_decorator with arg',) kwargs: {}
test f_006_3
$ 

クラスの継承を利用すると、スーパークラスでは引数の有無による呼び出し方の違いを吸収し、サブクラスでデコレートの実装に専念することができます。

まとめ

  • class によるデコレータの実装では、引数の有無によって __init__()__call__() の呼び出しタイミングに注意が必要。
  • 属性に注意。特に __name__ は class の初期属性が無いため、汎用的なデコレータとしては、明示的に設定が必要。
  • それ以外は、関数によるデコレータとほぼ同等に扱える。
  • クラスの継承を利用して、デコレータの本体実装部分を独立させられる。
  • 今回は、引数が有る場合でも無い場合でも両方を吸収するクラスを作ったが、どちらか一方だけで十分なことが多いと思われるので、それに応じて、最小限に留めるほうが、本来は望ましい。

decorator_class_framework.py

decorator_class_framework.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

###########################################
#   デコレータ (decorator)
###########################################
from functools import wraps

class my_decorator_base:
    def __init__( self, *args, **kwargs ):
        # func 引数の判定と引数の格納
        self._func = None
        self._args = []
        self._kwargs = {}
        if len(args) == 1 and callable(args[0]):
            # 引数なしのデコレータが呼び出された場合
            self._func = self._wrapper( args[0] )
        else:
            # 引数ありのデコレータが呼び出された場合
            self._args = args
            self._kwargs = kwargs

    def __call__( self, *args, **kwargs ):
        # func 引数の有無で判定
        if self._func is None:
            if len(args) == 1 and callable(args[0]):
                # 引数ありのデコレータが呼び出された場合
                self._func = self._wrapper( args[0] )
                return self._func
        else:
            # 引数なしのデコレータが呼び出された場合
            try:
                ret = self._func( *args, **kwargs )
            except:
                raise
            return ret

    def _wrapper( self, func ):
        # _wrapper() はサブクラスで実装する
        @wraps
        def wrapper_f( *args, **kwargs ):
            return func( *args, **kwargs )
        return wrapper_f


class my_decorator(my_decorator_base):
    """
    for doctest

    >>> @my_decorator
    ... def f1( arg1 ):
    ...     print( arg1 )
    ...
    >>> @my_decorator('mytest1')
    ... def f2( arg2 ):
    ...     print( arg2 )
    ...
    >>> @my_decorator
    ... def f3( arg1 ):
    ...     print( arg1 )
    ...     a = 1/0
    ...
    >>> @my_decorator('mytest2')
    ... def f4( arg2 ):
    ...     print( arg2 )
    ...     a = 1/0
    ...
    >>> try:
    ...     f1( "Hello, World! #1" )
    ... except:
    ...     print( "error #1" )
    ...
    前処理はここ
    called wrapper_f with args: ('Hello, World! #1',) kwargs: {} priv args: [] kwargs: {}
    Hello, World! #1
    後処理はここ
    >>> try:
    ...     f2( "Hello, World! #2" )
    ... except:
    ...     print( "error #2" )
    ...
    前処理はここ
    called wrapper_f with args: ('Hello, World! #2',) kwargs: {} priv args: ('mytest1',) kwargs: {}
    Hello, World! #2
    後処理はここ
    >>> try:
    ...     f3( "Hello, World! #3" )
    ... except:
    ...     print( "error #3" )
    ...
    前処理はここ
    called wrapper_f with args: ('Hello, World! #3',) kwargs: {} priv args: [] kwargs: {}
    Hello, World! #3
    error #3
    >>> try:
    ...     f4( "Hello, World! #4" )
    ... except:
    ...     print( "error #4" )
    ...
    前処理はここ
    called wrapper_f with args: ('Hello, World! #4',) kwargs: {} priv args: ('mytest2',) kwargs: {}
    Hello, World! #4
    error #4
    >>>
    """
    def __init__( self, *args, **kwargs ):
        super().__init__( *args, **kwargs )

    def _wrapper( self, func ):
        """デコレータ呼び出し本体"""
        @wraps(func)
        def wrapper_f( *args, **kwargs ):
            # 前処理はここ
            print( "前処理はここ" )
            print( "called wrapper_f with",
                "args:", args, "kwargs:", kwargs,
                "priv args:", self._args, "kwargs:", self._kwargs )
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            # 後処理はここ
            print( "後処理はここ" )
            return ret
        # wrapper_f 内で、__name__ と __doc__ を操作する場合には
        # 下の setattr は削除する
        setattr( self, "__name__", wrapper_f.__name__ )
        setattr( self, "__doc__", wrapper_f.__doc__ )
        return wrapper_f


###########################################
#   unitttest
###########################################
import unittest
from io import StringIO
import sys

class Test_My_Decorator(unittest.TestCase):
    def setUp(self):
        self.saved_stdout = sys.stdout
        self.stdout = StringIO()
        sys.stdout = self.stdout

    def tearDown(self):
        sys.stdout = self.saved_stdout

    def test_decorator_noarg(self):
        @my_decorator
        def t1(arg0):
            print( arg0 )

        t1("test_decorator_noarg")

        self.assertEqual(self.stdout.getvalue(),
            "前処理はここ\n"
            "called wrapper_f with args: ('test_decorator_noarg',) kwargs: {} priv args: [] kwargs: {}\n"
            "test_decorator_noarg\n"
            "後処理はここ\n"
            )

    def test_decorator_witharg(self):
        @my_decorator('with arg')
        def t1(arg0):
            print( arg0 )

        t1("test_decorator_witharg")

        self.assertEqual(self.stdout.getvalue(),
            "前処理はここ\n"
            "called wrapper_f with args: ('test_decorator_witharg',) kwargs: {} priv args: ('with arg',) kwargs: {}\n"
            "test_decorator_witharg\n"
            "後処理はここ\n"
            )

    def test_functionname(self):
        @my_decorator
        def t1():
            return t1.__name__

        f_name = t1()

        self.assertEqual( f_name, "t1" )

    def test_docattribute(self):
        @my_decorator
        def t1():
            """Test Document"""
            pass

        self.assertEqual( t1.__doc__, "Test Document" )


###########################################
#   main
###########################################
if __name__ == '__main__':

    @my_decorator
    def f1( arg1 ):
        print( arg1 )

    @my_decorator('mytest1')
    def f2( arg2 ):
        print( arg2 )

    @my_decorator
    def f3( arg1 ):
        print( arg1 )
        a = 1/0

    @my_decorator('mytest2')
    def f4( arg2 ):
        print( arg2 )
        a = 1/0

    try:
        f1( "Hello, World! #1" )
    except:
        print( "error #1" )

    try:
        f2( "Hello, World! #2" )
    except:
        print( "error #2" )

    try:
        f3( "Hello, World! #3" )
    except:
        print( "error #3" )

    try:
        f4( "Hello, World! #4" )
    except:
        print( "error #4" )

    import doctest
    doctest.testmod()

    unittest.main()
実行結果
$ python decorator_class_framework.py
前処理はここ
called wrapper_f with args: ('Hello, World! #1',) kwargs: {} priv args: [] kwargs: {}
Hello, World! #1
後処理はここ
前処理はここ
called wrapper_f with args: ('Hello, World! #2',) kwargs: {} priv args: ('mytest1',) kwargs: {}
Hello, World! #2
後処理はここ
前処理はここ
called wrapper_f with args: ('Hello, World! #3',) kwargs: {} priv args: [] kwargs: {}
Hello, World! #3
error #3
前処理はここ
called wrapper_f with args: ('Hello, World! #4',) kwargs: {} priv args: ('mytest2',) kwargs: {}
Hello, World! #4
error #4
....
----------------------------------------------------------------------
Ran 4 tests in 0.003s

OK
$
unittest 実行結果
$ python -m unittest -v decorator_class_framework.py
test_decorator_noarg (decorator_class_framework.Test_My_Decorator) ... ok
test_decorator_witharg (decorator_class_framework.Test_My_Decorator) ... ok
test_docattribute (decorator_class_framework.Test_My_Decorator) ... ok
test_functionname (decorator_class_framework.Test_My_Decorator) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK
$
16
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
18