关于python:decorator打印函数调用详细信息-参数名称和有效值

Decorator to print function call details - parameters names and effective values

我想创建一个函数,作为另一个函数的修饰器,它将打印该函数调用的详细信息-参数名称和有效值。我目前的实现就是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def describeFuncCall(func):
    '''Decorator to print function call details - parameters names and effective values'''
    def wrapper(*func_args, **func_kwargs):
        print 'func_code.co_varnames =', func.func_code.co_varnames
        print 'func_code.co_argcount =', func.func_code.co_argcount
        print 'func_args =', func_args
        print 'func_kwargs =', func_kwargs
        params = []
        for argNo in range(func.func_code.co_argcount):
            argName = func.func_code.co_varnames[argNo]
            argValue = func_args[argNo] if argNo < len(func_args) else func.func_defaults[argNo - func.func_code.co_argcount]
            params.append((argName, argValue))
        for argName, argValue in func_kwargs.items():
            params.append((argName, argValue))
        params = [ argName + ' = ' + repr(argValue) for argName, argValue in params]
        print(func.__name__ + ' ( ' +  ', '.join(params) + ' )')
        return func(*func_args, **func_kwargs)
    return wrapper


@describeFuncCall
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass


test(1)
#test(1, 3)
#test(1, d = 5)
test(1, 2, 3, 4, 5, d = 6, g = 12.9)

有点效果,但有一些缺陷:

打电话

test(1, 2, 3, 4, 5, d = 6, g = 12.9)

资讯科技印刷

test ( a = 1, b = 2, c = 3, d = 6, g = 12.9 )

预期结果是

test ( a = 1, b = 2, c = 3, args = [4, 5], kwargs = {'d': 6, 'g': 12.9} )

我被困在这里了。你能帮我找到正确的解决办法吗?


对不起,有点乱。我修改了http://wiki.python.org/moin/pythondecoratorlibrary easy dump的函数参数中的一些代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
def dump_args(func):
   "This decorator dumps out the arguments passed to a function before calling it"
    argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
    fname = func.func_name
    def echo_func(*args,**kwargs):
        print fname,"(", ', '.join(
            '%s=%r' % entry
            for entry in zip(argnames,args[:len(argnames)])+[("args",list(args[len(argnames):]))]+[("kwargs",kwargs)]) +")"
    return echo_func

@dump_args
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass

试验(1,2,3,4,5,d=6,g=12.9)

输出:

测试(a=1,b=2,c=3,args=[4,5],kwargs='d':6,'g':12.9)


这是python 3.6的更新版本+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import inspect

def dump_args(func):
   """Decorator to print function call details - parameters names and effective values.
   """

    def wrapper(*args, **kwargs):
        func_args = inspect.signature(func).bind(*args, **kwargs).arguments
        func_args_str =  ', '.join('{} = {!r}'.format(*item) for item in func_args.items())
        print(f'{func.__module__}.{func.__qualname__} ( {func_args_str} )')
        return func(*args, **kwargs)
    return wrapper

@dump_args
def test(a, b=4, c='blah-blah', *args, **kwargs):
    pass

test(1)
test(1, 3)
test(1, d=5)
test(1, 2, 3, 4, 5, d=6, g=12.9)

旧版本

使用默认值的工作版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def dumpArgs(func):
    '''Decorator to print function call details - parameters names and effective values'''
    def wrapper(*func_args, **func_kwargs):
        arg_names = func.func_code.co_varnames[:func.func_code.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.func_defaults or ()
        args = args + defaults[len(defaults) - (func.func_code.co_argcount - len(args)):]
        params = zip(arg_names, args)
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        print func.func_name + ' (' + ', '.join('%s = %r' % p for p in params) + ' )'
        return func(*func_args, **func_kwargs)
    return wrapper  

@dumpArgs
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass

test(1)
test(1, 3)
test(1, d = 5)
test(1, 2, 3, 4, 5, d = 6, g = 12.9)

结果:

1
2
3
4
>>> test  (  a = 1, b = 4, c = 'blah-blah' )
test  (  a = 1, b = 3, c = 'blah-blah' )
test  (  a = 1, b = 4, c = 'blah-blah', kwargs = {'d': 5} )
test  (  a = 1, b = 2, c = 3, args = (4, 5), kwargs = {'d': 6, 'g': 12.9} )


下面是我在python 3中解决它的方法,基于aliteralmind的答案,如果我可以这么说的话,可以更清晰地放置(pep8)。清理工作的大部分灵感来自罗伯特·金(Robert King)目前接受的答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import logging


def log_function_entry_and_exit(decorated_function):
   """
    Function decorator logging entry + exit and parameters of functions.

    Entry and exit as logging.info, parameters as logging.DEBUG.
   """

    from functools import wraps

    @wraps(decorated_function)
    def wrapper(*dec_fn_args, **dec_fn_kwargs):
        # Log function entry
        func_name = decorated_function.__name__
        log = logging.getLogger(func_name)
        log.info('Entering {}()...'.format(func_name))

        # get function params (args and kwargs)
        arg_names = decorated_function.__code__.co_varnames
        params = dict(
            args=dict(zip(arg_names, dec_fn_args)),
            kwargs=dec_fn_kwargs)

        log.debug(
           "\t" + ', '.join([
                '{}={}'.format(str(k), repr(v)) for k, v in params.items()]))
        # Execute wrapped (decorated) function:
        out = decorated_function(*dec_fn_args, **dec_fn_kwargs)
        log.info('Done running {}()!'.format(func_name))

        return out
    return wrapper


@log_function_entry_and_exit
def func1(a, b, c):
    print("
\ty'elo2!
"
)
@log_function_entry_and_exit
def a(x, y, z):
    print("
\ty'elo!
"
)

LOG_FORMAT = '[{}] !%(levelname)s! %(funcName)s: %(message)s'.format(
    _get_current_time_string(just_time_string=True))
logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)

a(x=1, y="b", z={'c': 2})
func1(2, b="y", c={'z': 4})
func1(2,"y", {'z': 4})

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
In [6]: a(x=1, y="b", z={'c': 2})
   ...: func1(2, b="y", c={'z': 4})
   ...: func1(2,"y", {'z': 4})
   ...:
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering a()...
[2016.09.22 - 17:31:48] !DEBUG! wrapper:        kwargs={'x': 1, 'z': {'c': 2}, 'y': 'b'}, args={}

        y'elo!

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running a()!
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering func1()...
[2016.09.22 - 17:31:48] !DEBUG! wrapper:        kwargs={'
c': {'z': 4}, 'b': 'y'}, args={'a': 2}

        y'
elo2!

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running func1()!
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering func1()...
[2016.09.22 - 17:31:48] !DEBUG! wrapper:        kwargs={}, args={'c': {'z': 4}, 'a': 2, 'b': 'y'}

        y'elo2!

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running func1()!

注意:输出中的wrapper字符串表示使logging.X()消息调用的任何对象的函数名。

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
In [1]: from meh import execute_os_command

In [2]: from meh import LOG_FORMAT

In [3]: import logging

In [4]:     logging.basicConfig(format=LOG_FORMAT, level=logging.INFO)
   ...:
   ...:     logging.info("Entered script...
"
)
   ...:
   ...:     result = execute_os_command(cmd=["echo","trololol"])
   ...:     print("
{}
"
.format(result))
   ...:     execute_os_command(cmd=["echo","trololol"], dry_run=True)
   ...:
   ...:     logging.info("Exiting script...
"
)
   ...:
[2016.09.22 - 17:42:19] !INFO! <module>: Entered script...

[2016.09.22 - 17:42:19] !INFO! wrapper: Entering execute_os_command()...
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Executing:
[2016.09.22 - 17:42:19] !INFO! execute_os_command:       echo trololol
[2016.09.22 - 17:42:19] !INFO! execute_os_command:   Waiting for above command to finish execution...
[2016.09.22 - 17:42:19] !INFO! wrapper: Done running execute_os_command()!

{'stderr': '', 'stdout': 'trololol
'
, 'command': ['echo', 'trololol'], 'returncode': 0, 'stdin': None, 'timedout': False}

[2016.09.22 - 17:42:19] !INFO! wrapper: Entering execute_os_command()...
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Would have executed:
[2016.09.22 - 17:42:19] !INFO! execute_os_command:       echo trololol
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Exiting execute_os_command()...
[2016.09.22 - 17:42:19] !INFO! wrapper: Done running execute_os_command()!
[2016.09.22 - 17:42:19] !INFO! <module>: Exiting script...


In [5]:

当我得到被称为"时间和能量"的神奇资源时,我很想和LOG_FORMAT一起玩,并弄清楚如何用say filename和linenumber of function invocation=替换wrapper子字符串。


@Warvariuc的答案,升级到python 3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def dumpArgs(func):
    '''Decorator to print function call details - parameters names and effective values'''
    def wrapper(*func_args, **func_kwargs):
        arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.__defaults__ or ()
        args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):]
        params = list(zip(arg_names, args))
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        print(func.__name__ + ' (' + ', '.join('%s = %r' % p for p in params) + ' )')
        return func(*func_args, **func_kwargs)
    return wrapper  

@dumpArgs
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass

test(1)
test(1, 3)
test(1, d = 5)
test(1, 2, 3, 4, 5, d = 6, g = 12.9)


这是一个有点老的帖子,但想增加我的一点。Warvariuc给出的解决方案并不适用于所有情况。如果一个方法具有默认值,并且我们在调用时发送命名参数,那么它不会给出正确的输出。例如,我们得到两个b值。

1
2
test(1, b = 5)
test (a = 1, b = 4, c = 'blah-blah', kwargs = {'b': 5} )

添加我修改的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def print_args(func):
"""
Function to print all args of decorated function
"""

def wrapper(*func_args, **func_kwargs):
    arg_names = func.func_code.co_varnames[:func.func_code.co_argcount]
    args = func_args[:len(arg_names)]
    defaults = func.func_defaults or ()
    args = args + defaults[len(defaults) - (func.func_code.co_argcount - len(args)):]
    params = zip(arg_names, args)
    new_arg_list = [list(i) for i in params]
    for key in func_kwargs:
        for param in new_arg_list:
            if key == param[0]:
                param[1] = func_kwargs[key]
    new_arg_list = [tuple(i) for i in new_arg_list]
    print func.func_name + ' (' + ', '.join('%s = %r' % p for p in new_arg_list) + ' )'
    return func(*func_args, **func_kwargs)
return wrapper


@print_args
def test_params(a=7,b=5):
    pass

test_params(a=3)

产量

1
test_params (a = 3, b = 5 )