Python

Pythonのデコレータ動作メモ

More than 1 year has passed since last update.

デコレータを活用したPythonプログラミングを勉強していると、その場では、理解できても、すぐに、忘れてしまいますよね。
そこで、今回、動的にメソッドを追加するサンプルアプリを、実際に動作させてみて、各種パラメータが、どのような値を保持するかを、メモっておきました。

■ デコレータを活用して、動的にメソッドを追加してみる

(1) サンプルプログラム

引数をもつメソッド(func1, func2)をデコレートするサンプルプログラムになります。

sample1.py
import sys
import json
import logging

logging.basicConfig()
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)


def api_method(api_name):
    def _api_method(fn):
        log.debug("fn={}".format(fn))
        log.debug("api_name={}".format(api_name))
        setattr(fn, '__api_name__', api_name)
        return fn
    return _api_method

class Sample(object):
    def __init__(self):
        log.debug(json.dumps(dir(self), sort_keys=False, indent=4))

        self._api_methods = {}
        for method_name in dir(self):
            method = getattr(self, method_name)
            try:
                name = getattr(method, '__api_name__')
                msg = 'API method {0} registered'.format(name)
                log.debug(msg)
                self._api_methods[name] = method
            except AttributeError:
                pass

        log.debug("_api_methods=[%s]"%self._api_methods)

    @api_method('func1')
    def _func1(self, params):
        print ("*** func1 : params=[%s]"%params)
        return "func1 is done!"

    @api_method('func2')
    def _func2(self, params):
        print ("*** func2 : params=[%s]"%params)
        return "func2 is done!"


if __name__ == '__main__':
    args = sys.argv
    if len(args) == 3:
        method = args[1]
        params = args[2]

    m = Sample()
    result = m._api_methods[method](params)
    print ("*** result=[%s]"%result)

(2) 実際に動かしてみる

まずは、func1メソッドを動かしてみる

$ python sample1.py func1 aaa
DEBUG:__main__:fn=<function Sample._func1 at 0x10dde60d0>
DEBUG:__main__:api_name=func1
DEBUG:__main__:fn=<function Sample._func2 at 0x10dde6158>
DEBUG:__main__:api_name=func2
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "_func1",
    "_func2"
]
DEBUG:__main__:API method func1 registered
DEBUG:__main__:API method func2 registered
DEBUG:__main__:_api_methods=[{'func1': <bound method Sample._func1 of <__main__.Sample object at 0x10ddde898>>, 'func2': <bound method Sample._func2 of <__main__.Sample object at 0x10ddde898>>}]
*** func1 : params=[aaa]
*** result=[func1 is done!]

つづいて、func2メソッドを動かしてみる

$ python sample1.py func2 bbb
DEBUG:__main__:fn=<function Sample._func1 at 0x10f9790d0>
DEBUG:__main__:api_name=func1
DEBUG:__main__:fn=<function Sample._func2 at 0x10f979158>
DEBUG:__main__:api_name=func2
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "_func1",
    "_func2"
]
DEBUG:__main__:API method func1 registered
DEBUG:__main__:API method func2 registered
DEBUG:__main__:_api_methods=[{'func1': <bound method Sample._func1 of <__main__.Sample object at 0x10f971908>>, 'func2': <bound method Sample._func2 of <__main__.Sample object at 0x10f971908>>}]
*** func2 : params=[bbb]
*** result=[func2 is done!]

正しく起動することができました。

■ デコレータを使用せず、動的にメソッドを追加してみる

(1) サンプルプログラム

デコレートを使用せずに、同じように動作するサンプルプログラムになります。

sample2.py
import sys
import json
import logging

logging.basicConfig()
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)


class Sample(object):
    def __init__(self):
        log.debug(json.dumps(dir(self), sort_keys=False, indent=4))

    def func1(self, params):
        print ("*** func1 : params=[%s]"%params)
        return "func1 is done!"

    def func2(self, params):
        print ("*** func2 : params=[%s]"%params)
        return "func2 is done!"


if __name__ == '__main__':
    args = sys.argv
    if len(args) == 3:
        method = args[1]
        params = args[2]

    m = Sample()
    meth = getattr(m, method, None)
    if meth:
        result = meth(params)
        print ("*** result=[%s]"%result)

(2) 実際に動かしてみる

まずは、func1メソッドを動かしてみる

$ python sample2.py func1 aaa
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "func1",
    "func2"
]
*** func1 : params=[aaa]
*** result=[func1 is done!]

つづいて、func2メソッドを動かしてみる

$ python sample2.py func2 bbb
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "func1",
    "func2"
]
*** func2 : params=[bbb]
*** result=[func2 is done!]

こちらも、正しく起動することができました。

■さらに、拡張してみる

動的にメソッドを追加するアプリを作成する際に、デコレータを作成しない方が、シンプルにコードが書ける気がするけど、敢えて、デコレータを活用した方が望ましい場面は、どんな場合なんだろうか?
単純に、DEBUG文を追加するなどの、メソッドを装飾する目的だと考えるので、さらに拡張してみた。

(1) サンプルプログラム

sample3.py
import sys
import json
import logging

logging.basicConfig()
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)


def api_method(api_name):
    def _api_method(fn):
        log.debug("fn={}".format(fn))
        log.debug("api_name={}".format(api_name))
        def _wrap(*args, **kwargs):
            log.debug("-- 前処理 -- [{}]".format(api_name))
            ret = fn(*args, **kwargs)
            log.debug("-- 後処理 -- [{}]".format(api_name))
            return ret
        return _wrap
    return _api_method

class Sample(object):
    def __init__(self):
        log.debug(json.dumps(dir(self), sort_keys=False, indent=4))

    @api_method('func1')
    def func1(self, params):
        print ("*** func1 : params=[%s]"%params)
        return "func1 is done!"

    @api_method('func2')
    def func2(self, params):
        print ("*** func2 : params=[%s]"%params)
        return "func2 is done!"


if __name__ == '__main__':
    args = sys.argv
    if len(args) == 3:
        method = args[1]
        params = args[2]

    m = Sample()
    meth = getattr(m, method, None)
    if meth:
        result = meth(params)
        print ("*** result=[%s]"%result)

(2) 実際に動かしてみる

まずは、func1メソッドを動かしてみる

$ python sample3.py func1 aaa
DEBUG:__main__:fn=<function Sample.func1 at 0x10aba10d0>
DEBUG:__main__:api_name=func1
DEBUG:__main__:fn=<function Sample.func2 at 0x10aba11e0>
DEBUG:__main__:api_name=func2
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "func1",
    "func2"
]
DEBUG:__main__:-- 前処理 -- [func1]
*** func1 : params=[aaa]
DEBUG:__main__:-- 後処理 -- [func1]
*** result=[func1 is done!]

つづいて、func2メソッドを動かしてみる

$ python sample3.py func2 bbb
DEBUG:__main__:fn=<function Sample.func1 at 0x106a1d0d0>
DEBUG:__main__:api_name=func1
DEBUG:__main__:fn=<function Sample.func2 at 0x106a1d1e0>
DEBUG:__main__:api_name=func2
DEBUG:__main__:[
    "__class__",
    "__delattr__",
    "__dict__",
    "__dir__",
    "__doc__",
    "__eq__",
    "__format__",
    "__ge__",
    "__getattribute__",
    "__gt__",
    "__hash__",
    "__init__",
    "__init_subclass__",
    "__le__",
    "__lt__",
    "__module__",
    "__ne__",
    "__new__",
    "__reduce__",
    "__reduce_ex__",
    "__repr__",
    "__setattr__",
    "__sizeof__",
    "__str__",
    "__subclasshook__",
    "__weakref__",
    "func1",
    "func2"
]
DEBUG:__main__:-- 前処理 -- [func2]
*** func2 : params=[bbb]
DEBUG:__main__:-- 後処理 -- [func2]
*** result=[func2 is done!]

いい感じに動作するようになりましたね。
以上です