python 打印函数调用细节(参数名和值)的装饰器?

wgx48brx  于 2023-03-16  发布在  Python
关注(0)|答案(6)|浏览(154)

我想做一个函数,作为另一个函数的装饰器,它将打印该函数调用的细节-参数名和有效值。

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} )
我被困在这里了。你能帮我找到正确的解决方案吗?

x6h2sr28

x6h2sr281#

以下是Python 3.6+的更新版本

import inspect
from functools import wraps

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

    This includes parameters names and effective values.
    """

    @wraps(func)
    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} )
6mw9ycah

6mw9ycah2#

对不起,有点乱。我修改了一些代码从轻松转储函数参数在PythonDecoratorLibrary

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

kzipqqlq3#

下面是我在Python 3中解决这个问题的方法,基于aliteralmind's答案,如果我可以这么说的话,放得更干净(PEP 8)。清理的大部分灵感来自(当前)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年6月11日):修复了@Gahan评论(日期:2020年6月10日)提示后的wrapper问题。

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

aij0ehis

aij0ehis4#

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

vcudknz35#

下面是一个同样适用于本机函数的版本(只适用于Python 3)

import logging
import inspect

def dump_args(func):
    """
    Decorator to print function call details.
    This includes parameters names and effective values.
    """
    def wrapper(*args, **kwargs):
        try:
            # For standard functions, inspect the signature
            signature = inspect.signature(func)
            func_args = signature.bind(*args, **kwargs).arguments
            func_args_str = ", ".join(map("{0[0]} = {0[1]!r}".format, func_args.items()))
            msg =  f"{func.__module__}.{func.__qualname__} ( {func_args_str} )"
        except ValueError:
            # For native functions, the signature cannot be inspected
            args_strs = map(lambda arg: f"arg_{arg[0]} = {arg[1]}", enumerate(args) )
            kwargs_strs = map(lambda kwarg: f"{kwarg[0]} = {kwarg[1]}", kwargs )
            func_args_str = ", ".join(list(args_strs) + list(kwargs_strs))
            msg =  f"{func.__module__}.{func.__name__} ( {func_args_str} )"
        logging.debug(msg)
        return func(*args, **kwargs)
    return wrapper

我可以在模块级别激活它,如下所示:
__init__.py

_DEBUG_MY_MODULE = True

if _DEBUG_MY_MODULE:
    from .decorators import dump_args
    SDL_CreateWindow = dump_args(SDL_CreateWindow)

我得到的输出是这样的:

DEBUG:root:ctypes.SDL_CreateWindow ( arg_0 = b'', arg_1 = 863, arg_2 = 548, arg_3 = 150, arg_4 = 150, arg_5 = 8226 )

有关包括返回值、输出参数和花哨输出的更完整版本,请参见this gist

mqkwyuun

mqkwyuun6#

这是一个有点老的职位,但想添加我的位。解决方案所给的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.__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 = 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]
        result = func(*func_args, **func_kwargs)
        print(f'{func.__name__} (' + ', '.join('%s = %r' % p for p in new_arg_list) + f'): {result}')

        return result

    return wrapper
    

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

产出

test_params (a = 3, b = 5)

相关问题