LoginSignup
0
1

More than 1 year has passed since last update.

デコレータがついているとdocstringが取れない?

Last updated at Posted at 2021-05-05

久々に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]

見事解決!?

0
1
2

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
0
1