たんまり5分は悩んでしまった。バカだ。
もともと関数への出入りをデバッグ用にロギングしたくて:
1 import logging
2 import inspect
3 from functools import wraps
4
5 def logging_enter_debug():
6 def decorator(func):
7 @wraps(func)
8 def wrapper(*args, **kwargs):
9 logger = logging.getLogger(
10 inspect.getmodule(
11 inspect.stack()[1][0]).__name__)
12 logger.debug("START '%s'", func.__name__)
13 try:
14 return func(*args, **kwargs)
15 except Exception as e:
16 logger.debug("! failed with <%s>" % e.__class__.__name__)
17 raise
18 finally:
19 logger.debug("END '%s'", func.__name__)
20 return wrapper
21 return decorator
としてたとする。これによって、
1 class Hoge(object):
2 @logging_enter_debug()
3 def hoge(self, ...):
4 # ...
みたいに使えてハッピーだ、てわけだ。
けどこれ、「時々 debug じゃなく info にしたいかも/したくなったなぁ」時、コピペで二重化するのもやなので、引数で切り替えられるようにしようか、と。ただしアプリケーション全編で使いまくっているので、クライアントコードを壊すことなく。
無論愚直な答えは当たり前のもの:
1 import logging
2 import inspect
3 from functools import wraps
4
5 def logging_enter(level):
6 def decorator(func):
7 @wraps(func)
8 def wrapper(*args, **kwargs):
9 logger = logging.getLogger(
10 inspect.getmodule(
11 inspect.stack()[1][0]).__name__)
12 puts = logger.debug if level else logger.info
13 puts("START '%s'", func.__name__)
14 try:
15 return func(*args, **kwargs)
16 except Exception as e:
17 puts("! failed with <%s>" % e.__class__.__name__)
18 raise
19 finally:
20 puts("END '%s'", func.__name__)
21 return wrapper
22 return decorator
23
24 def logging_enter_info():
25 return logging_enter(0)
26
27 def logging_enter_debug():
28 return logging_enter(1)
partial を使ってもうほんの少しだけスマートに:
1 import logging
2 import inspect
3 from functools import wraps, partial
4
5 def logging_enter(level):
6 def decorator(func):
7 @wraps(func)
8 def wrapper(*args, **kwargs):
9 logger = logging.getLogger(
10 inspect.getmodule(
11 inspect.stack()[1][0]).__name__)
12 puts = logger.debug if level else logger.info
13 puts("START '%s'", func.__name__)
14 try:
15 return func(*args, **kwargs)
16 except Exception as e:
17 puts("! failed with <%s>" % e.__class__.__name__)
18 raise
19 finally:
20 puts("END '%s'", func.__name__)
21 return wrapper
22 return decorator
23
24
25 logging_enter_info = partial(logging_enter, level=0)
26 logging_enter_debug = partial(logging_enter, level=1)
これだけ。
ただその「5分の迷走」で最初書いちゃったのは:
1 import logging
2 import inspect
3 from functools import wraps
4
5 class _mk_logging_enter_deco():
6 def __init__(self, level):
7 self.level = level
8
9 def __call__(self, func):
10 @wraps(func)
11 def wrapper(*args, **kwargs):
12 logger = logging.getLogger(
13 inspect.getmodule(
14 inspect.stack()[1][0]).__name__)
15 puts = logger.debug if self.level else logger.info
16 puts("START '%s'", func.__name__)
17 try:
18 return func(*args, **kwargs)
19 except Exception as e:
20 puts("! failed with <%s>" % e.__class__.__name__)
21 raise
22 finally:
23 puts("END '%s'", func.__name__)
24 return wrapper
25
26
27 def logging_enter_info():
28 return _mk_logging_enter_deco(0)
29
30
31 def logging_enter_debug():
32 return _mk_logging_enter_deco(1)
無論これも解の一つではあるし問題なく期待通りのものだから「大馬鹿」ではないけれど、このためにあえて class にするのはツーマッチではあろう。
なんだかんだデコレータのソースって日常にしてないと迷子になりやすいんで、時折脳内でこんなことが起こったりするんだよね。(慣れていない人は、pdb なりや trace あるいは脳内でも紙面ででもマウスでもいいから、ステップ実行して追いかけてみるといい。)