【问题标题】:Decorator that prints function call details (parameters names and effective values)?打印函数调用详细信息(参数名称和有效值)的装饰器?
【发布时间】:2011-09-06 05:09:45
【问题描述】:

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

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

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

【问题讨论】:

  • 如果这是为了调试,显示函数返回值可能也没有用吗?
  • @Curt 是的,当然

标签: python


【解决方案1】:

抱歉,有点乱。我在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

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

输出:

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

【讨论】:

  • 它有一些我修复的错误,但它没有考虑默认值:test(1): test (a = 1, args = (), kwargs = {})
  • 不幸的是,这个例子根本没有调用包装函数——你不会在“test()”调用中发现任何错误。
  • 请注意,这适用于 python 2。请参阅 Python 3 的其他答案。
【解决方案2】:

这里是 Python 3.6+ 的更新版本

import inspect


def dump_args(func):
    """
    Decorator to print function call details.

    This includes parameters names and effective values.
    """

    def wrapper(*args, **kwargs):
        func_args = inspect.signature(func).bind(*args, **kwargs).arguments
        func_args_str = ", ".join(map("{0[0]} = {0[1]!r}".format, 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)

旧版本


具有默认值的工作版本:

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

【讨论】:

  • 这个问题很老但仍然有用。您应该注意,核心库公开了一种标准方法来执行从 *args**kwargs 到实际参数名称的映射:Signature.bind
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    这是我在 Python 3 中解决它的方法,基于aliteralmind's 答案,如果我可以这么说的话,可以说得更干净(PEP8)。 清理的大部分灵感来自(当前)accepted answer by Robert King

    代码(test.py):

    #!/usr/bin/env python3
    
    import functools
    import inspect
    import logging
    import time
    
    
    class CustomFormatter(logging.Formatter):
        """
        Custom formatter, overrides funcName with value of name_override if it exists
    
        Inspired by; https://stackoverflow.com/a/7004565/1503549
        """
        def format(self, record):
            if hasattr(record, 'func_name'):
                record.funcName = record.func_name
            return super(CustomFormatter, self).format(record)
    
    
    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.
        """
    
        @functools.wraps(decorated_function)
        def wrapper(*dec_fn_args, **dec_fn_kwargs):
            # Log function entry
            func_name = decorated_function.__name__
            name_dict = dict(func_name=func_name)
            logging.info(f"Entering {func_name}()...", extra=name_dict)
    
            # Log function params (args and kwargs)
            func_args = inspect.signature(decorated_function).bind(*dec_fn_args, **dec_fn_kwargs).arguments
            func_args_str = ', '.join(
                f"{var_name} = {var_value}"
                for var_name, var_value
                in func_args.items()
            )
            logging.debug(f"\t{func_args_str}", extra=name_dict)
    
            # Execute wrapped (decorated) function:
            out = decorated_function(*dec_fn_args, **dec_fn_kwargs)
    
            time.sleep(1)   # Test to ensure timestamp is real
            logging.info(f"Done running {func_name}()!", extra=name_dict)
    
            return out
        return wrapper
    
    
    @log_function_entry_and_exit
    def func2(*args, **kwargs):
        print('\t\thello')
    
    
    @log_function_entry_and_exit
    def func1(a, b, c):
        func2(1, 2, 3, 4, b=5)
        print('Hello2!')
    
    
    if __name__ == '__main__':
        log = logging.getLogger()
    
        # Must get handler to set format
        handler = logging.StreamHandler()   # Get default root logger
        handler.setFormatter(
            CustomFormatter(
                (
                    '[%(asctime)s]'
                    ' %(levelname)s:%(funcName)s():%(lineno)s>'
                    ' %(message)s'
                ),
                datefmt='%Y-%m-%dT%H:%M:%S',
            )
        )
    
        # Set logLevel
        log.setLevel(logging.DEBUG)
        handler.setLevel(logging.DEBUG)
    
        # Combine the two again
        log.addHandler(handler)
    
        log.info('yolo!', extra=dict(func_name='__main__'))
    
        func1(2, b="y", c={'z': 4})
    

    输出:

    [2020-06-11 22:22:15] 0 x10an14@x10-desktop:~/Desktop/testy
    -> $ python3 test.py 
    [2020-06-11T22:22:53] INFO:__main__():88> yolo!
    [2020-06-11T22:22:53] INFO:func1():33> Entering func1()...
    [2020-06-11T22:22:53] DEBUG:func1():42>     a = 2, b = y, c = {'z': 4}
    [2020-06-11T22:22:53] INFO:func2():33> Entering func2()...
    [2020-06-11T22:22:53] DEBUG:func2():42>     args = (1, 2, 3, 4), kwargs = {'b': 5}
            hello
    [2020-06-11T22:22:54] INFO:func2():48> Done running func2()!
    Hello2!
    [2020-06-11T22:22:55] INFO:func1():48> Done running func1()!
    [2020-06-11 22:22:55] 0 x10an14@x10-desktop:~/Desktop/testy
    -> $ 
    

    当我得到被称为“时间和精力”的神奇资源时,我有兴趣玩弄LOG_FORMAT,并弄清楚如何用文件名和函数调用的行号替换wrapper 子字符串=)

    编辑(2020-06-11):在@Gahan 的评论(日期为 2020-06-10)提示后修复了 wrapper 问题。

    似乎不可能(参考https://stackoverflow.com/a/8339710/1503549)有一个包装报告(通过logging 模块)包装/装饰函数的行号。也许wrapt 可以用于此目的?

    【讨论】:

    • wrapper 替换函数调用的文件名和行号的任何解决方案?
    【解决方案5】:

    这是一个有点旧的帖子,但想添加我的一点。 warvariuc 给出的解决方案并非在所有情况下都有效。如果一个方法具有默认值,并且我们在调用时发送命名参数,它不会给出正确的输出。例如我们得到两个 b 值。

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

    添加我修改后的代码。

    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)
    

    输出

    test_params (a = 3, b = 5 )
    

    【讨论】:

      猜你喜欢
      • 2014-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多