2011-06-01 59 views
12

我想做一个函数,作为装饰器到另一个函数将打印该函数调用细节 - 参数名称和有效值。我目前的实施是这样的。修饰器打印函数调用细节 - 参数名称和有效值

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})

我被困在这里。你能帮我找到正确的解决方案吗?

+0

如果这是为了调试,也可能没有用也显示函数返回值吗? – Curt 2017-05-11 02:17:27

+0

@Curt是的,当然 – warvariuc 2017-05-11 04:37:43

回答

11

对不起,它有点凌乱。我修改一些代码从http://wiki.python.org/moin/PythonDecoratorLibrary#Easy_Dump_of_Function_Arguments

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})

+0

它有一些我固定的错误,但它没有考虑默认值: 'test(1)': test(a = 1,args =(),kwargs = {}) – warvariuc 2011-06-01 13:15:22

6

工作版本的默认值:

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) 

结果:

>>> 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}) 
+2

这个问题很旧,但仍然有用。你应该注意到,核心库公开了一个标准的方法来执行从'* args'和'** kwargs'到实际参数名称的映射:[Signature.bind](https://docs.python.org/3/library /inspect.html#inspect.Signature.bind) – Demurgos 2016-07-21 10:18:53

4

@ warvariuc的回答,升级到Python 3:

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) 
+0

感谢@Ffisegydd,对于'list(zip(...))'部分。 – aliteralmind 2014-08-08 14:48:43

4

这里是我如何解决它在Python 3,基于aliteralmind's答案,把更多的清洁(PEP8)如果我可以这么说。 清理灵感的多数来自(当前)accepted answerRobert King

代码:

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("\n\ty'elo2!\n") 
@log_function_entry_and_exit 
def a(x, y, z): 
    print("\n\ty'elo!\n") 

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}) 

输出:

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()消息调用函数名。

示例中使用:

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...\n") 
    ...: 
    ...:  result = execute_os_command(cmd=["echo", "trololol"]) 
    ...:  print("\n{}\n".format(result)) 
    ...:  execute_os_command(cmd=["echo", "trololol"], dry_run=True) 
    ...: 
    ...:  logging.info("Exiting script...\n") 
    ...: 
[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\n', '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玩耍,并找出如何我可以更换wrapper子说文件名和功能调用的linenumber =)