久々にPythonの記事です。
なんかハマった(そして解決できていない)ので、書いておくことにしました。
※ 追記あり
事象
docstringを取得するためのテスト用に以下のスクリプトを書きました。
test.py
import re
class Test:
def __init__(self):
pass
@classmethod
def hoge(cls):
"""メソッド一覧."""
methods = [a for a in dir(cls) if '_' not in a]
for _method in methods:
docstring = re.sub(' {1,}', '', getattr(cls, _method).__doc__)
args = re.split(r'\.\n', docstring)
title = args[0]
description = '\n'.join((''.join(args[1:])).split('\n')[1:])
print(_method)
print(title)
print(description)
return
def fuga(self, arg1):
"""引数をprintするだけ.
ただそれだけ。
"""
print(arg1)
def foo(self, arg2):
"""引数は全く関係ない.
いらないね。
"""
return 'foo'
def main():
Test.hoge()
if __name__ == '__main__':
main()
このtest.py
をそのまま実行すると、結果は以下になります。
foo
引数は全く関係ない
いらないね。
fuga
引数をprintするだけ
ただそれだけ。
hoge
メソッド一覧.
docstringが取得できていますね。
そこで、ロギング用に以下のデコレータを作成しました。
decos.py
def log(logger):
"""ロギングする."""
def log_wrapper(func):
"""ロギングラッパー."""
def wrapper(*args, **kwargs):
"""ラッパー実体."""
logger.info(f"--START-- {func.__name__}")
res = func(*args, **kwargs)
logger.debug(f"res: {res}")
logger.info(f"-- END -- {func.__name__}")
return res
return wrapper
return log_wrapper
実行をロギングしたかっただけです。
このデコレータはロガーを受け取って使用するタイプなので、test.py
に手を加えます。
test.py
import logging
import re
from decos import log
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s [%(filename)s in %(lineno)d]')
stream_handler.setFormatter(formatter)
LOGGER.addHandler(stream_handler)
class Test:
def __init__(self):
pass
@classmethod
def hoge(cls):
"""メソッド一覧."""
methods = [a for a in dir(cls) if '_' not in a]
for _method in methods:
docstring = re.sub(' {1,}', '', getattr(cls, _method).__doc__)
args = re.split(r'\.\n', docstring)
title = args[0]
description = '\n'.join((''.join(args[1:])).split('\n')[1:])
print(_method)
print(title)
print(description)
return
@log(LOGGER)
def fuga(self, arg1):
"""引数をprintするだけ.
ただそれだけ。
"""
print(arg1)
@log(LOGGER)
def foo(self, arg2):
"""引数は全く関係ない.
いらないね。
"""
return 'foo'
def main():
Test.hoge()
test = Test()
test.fuga('ふがふが')
if __name__ == '__main__':
main()
これで、メソッドの実行前後にログ出力するようになりました。
実行してみると・・・
foo
ラッパー実体.
fuga
ラッパー実体.
hoge
メソッド一覧.
2021-05-05 10:35:44,750 [INFO] --START-- fuga [decos.py in 9]
ふがふが
2021-05-05 10:35:44,750 [INFO] -- END -- fuga [decos.py in 12]
こんな風に、デコレータのdocstringに置き換わってしまいます。
何故・・・
解決策が分かれば追記したいと思います。
追記
コメントを受けて、強引ですが解決策が分かりました。
@kts_h さん、ありがとうございます。
デコレータを以下のように修正します。
decos.py
def log(logger):
"""ロギングする."""
def log_wrapper(func):
"""ロギングラッパー."""
def wrapper(*args, **kwargs):
"""ラッパー実体."""
logger.info(f"--START-- {func.__name__}")
res = func(*args, **kwargs)
logger.debug(f"res: {res}")
logger.info(f"-- END -- {func.__name__}")
return res
wrapper.__doc__ = func.__doc__
return wrapper
return log_wrapper
めっちゃ強引ですが、__doc__
を書き換えます。
これで実行してみると、出力が以下になりました。
foo
引数は全く関係ない
いらないね。
fuga
引数をprintするだけ
ただそれだけ。
hoge
メソッド一覧.
2021-05-05 12:39:47,891 [INFO] --START-- fuga [decos.py in 9]
ふがふが
2021-05-05 12:39:47,891 [INFO] -- END -- fuga [decos.py in 12]
見事解決!?