我想制作一个函数,作为另一个函数的装饰器,它将打印该函数调用的详细信息——参数名称和有效值。我目前的实现是这样的。
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)
Kinda工作,但有一些错误:
用于呼叫
test(1, 2, 3, 4, 5, d = 6, g = 12.9)
它打印
CCD_ 2。
预期结果为
test ( a = 1, b = 2, c = 3, args = [4, 5], kwargs = {'d': 6, 'g': 12.9} )
我被困在这里了。你能帮我找到正确的解决方案吗?
以下是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} )
抱歉有点乱。我修改了PythonDecoratorLibrary中函数参数的Easy Dump中的一些代码。
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})
以下是我在Python3中解决它的方法,基于alitermind的答案,如果可以的话,放得更干净(PEP8)。清理工作的大部分灵感来自罗伯特·金(目前)接受的答案。
代码(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('tthello')
@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
模块)包装/装饰函数的行号。也许wrpt可以用于此目的?
@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)
这里有一个版本也适用于本机函数(仅适用于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 )
要获得更完整的版本,包括返回值、输出参数和花式输出,请参阅以下要点
这是一篇有点旧的帖子,但想添加我的一点。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)