我需要一个装饰器来记录方法调用。我将使用这样的东西(打印将被替换为在某处发送数据):
def log(function):
varnames = function.__code__.co_varnames[:function.func_code.co_argcount]
defaults = {}
varnames_list = list(varnames)
for default in reversed(foo.func_defaults):
defaults[varnames_list.pop()] = default
@functools.wraps(function)
def decorator(*arg, **kargs):
result = function(*arg, **kargs)
incoming_args = defaults
incoming_args.update(kargs)
for i, value in enumerate(arg):
incoming_args[varnames[i]] = value
obj_to_log = {'function':function.__name__, 'incoming_args':incoming_args, "result":result}
print('log', obj_to_log)
return result
return decorator
pass
它工作正常,但它会影响原始方法签名:
def print_function_args(function):
argcount = function.func_code.co_argcount
print(function.__name__,
'argnames:', function.__code__.co_varnames[:argcount],
'defaults:', function.func_defaults)
def foo(a, b='default', c=42):
pass
print_function_args(foo)
# prints: foo argnames: ('a', 'b', 'c') defaults: ('default', 42)
@log
def bar(a, b='default', c=42):
pass
print_function_args(bar)
# bar argnames: () defaults: None
我必须保持方法签名不变,所以
有没有办法用装饰器包装函数,保留它的签名和文档?
答案 0 :(得分:1)
这里的主要问题是你直接从函数的代码对象中获取函数参数。这不是你想要的 - 你实际上对函数的代码并不感兴趣,你对函数本身感兴趣。装饰器有一个特殊的__wrapped__
属性,指向原始函数;但代码对象没有。因此,您应该使用inspect
module代替,这将为您处理大部分事情,并为您提供您期望的结果。
不幸的是,维护已修饰函数的签名必须通过将signature object分配给decorator.__signature__
来手动完成。
以下是使用python 3的log
模块的inspect
装饰器的实现:
def log(function):
signature = inspect.signature(function)
@functools.wraps(function)
def decorator(*args, **kwargs):
result = function(*args, **kwargs)
incoming_args = signature.bind(*args, **kwargs)
incoming_args.apply_defaults()
incoming_args = dict(incoming_args.arguments)
obj_to_log = {'function': function.__name__,
'incoming_args': incoming_args,
"result": result}
print('log', obj_to_log)
return result
# maintain the decorated function's signature
decorator.__signature__ = signature
return decorator
使用getfullargspec
可以简化(并更正)print_function_args
函数:
def print_function_args(function):
argspec = inspect.getfullargspec(function)
print(function.__name__,
'argnames:', argspec.args,
'defaults:', argspec.defaults,
'docstring:', function.__doc__)
你会发现现在一切正常:
def foo(a, b='default', c=42):
"""sample docstring"""
@log
def bar(a, b='default', c=42):
"""sample docstring"""
print_function_args(foo)
print_function_args(bar)
bar(3)
foo argnames: ['a', 'b', 'c'] defaults: ('default', 42) docstring: sample docstring
bar argnames: ['a', 'b', 'c'] defaults: ('default', 42) docstring: sample docstring
log {'function': 'bar', 'incoming_args': {'a': 3, 'b': 'default', 'c': 42}, 'result': None}