Edited at

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!]

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

以上です