walkingmask’s development log

IT系の情報などを適当に書いていきます

MENU

Pythonでの関数名取得(lamdaに渡した関数名+デコレータ関数を通常の関数呼び出しする)

普通の関数定義であれば、

def test_function():
    print(1)

print(test_function.__name__)
# => test_function

簡単ですね。では lambdaではどうでしょう?

test_lambda = lambda: test_function()

print(test_lambda.__name__)
# => <lambda>

当然、取れません。inspect等でごにょごにょしても、関数の内部定義まで取れたりはしません。

苦し紛れに、次のようにはできます。

import inspect
import re

print(re.findall('lambda: *(.+)\(', inspect.getsource(lambda_func))[0])
# => test_lambda

ただし、REPLではエラーとなります。

経緯とか

こんな記事があって、ちょうど時間計測ツールがあったら良いなと思っていたところだったのでパッケージ化しました。

GitHub - walkingmask/wtimeit: The wrapper of timeit

ただ、Python の buil-in モジュールである timeit も利用したかったのと、関数名を自動で取得して表示する方向にしたかったので、色々やってた感じです。

最初 timeitd (timeit decorator) にしようと思ったら既にあって、wtimeit (wrapper of timeit, walkingmask's timeit) にしましたw

追加で、関数名を使い分けるのではなく、decorator or notで動作を変更するようにしたかったのですが、関数側の引数が可変長だと難しいかもなとか、decorator での呼び出しなのか普通の呼び出しなのか判断が面倒そう(一応 inspect でできる)とかで諦めました。参考にした Django のデコレータ

と思ったけど、timeitd を見ると、デコレータの外側の関数(なんて言うんだ)を可変長にすればいけるかもしれない感じ。更新するかも。

追記: 2019/02/27

デコレータの外側の関数(なんて言うんだ)の、outer_wrapperの前に関数が引数にある場合の処理を書けば普通にいけました。

def timeit(lambda_func=None, repeat=1, unit='', ndigits=5):
    if lambda_func:
        return wtimeit(lambda_func, repeat, unit, ndigits)
    def outer_wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            return wtimeit(lambda: func(*args, **kwargs),
                           repeat, unit, ndigits, func.__name__)
        return inner_wrapper
    return outer_wrapper

こんな感じですね。これで

@wtimeit.timeit()
def test_decorator():
    print(1)

def test_func():
    print(1)

result = test_decorator()
result = wtimeit.timeit(lambda: test_func())

といった感じで、デコレータとしても buit-in timeit 的な使い方もできます。この書き方普通に嬉しい。