我正在尝试将两个装饰器教程组合到一个装饰器中,该装饰器将在指定的日志级别记录函数参数。
第一个教程来自here,看起来像这样(并按预期工作):
import logging
logging.basicConfig(level=logging.DEBUG)
def dump_args(func):
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = func.func_name
logger = logging.getLogger(fname)
def echo_func(*args, **kwargs):
"""
Log arguments, including name, type and value
"""
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.debug(" args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
return func(*args, **kwargs)
return echo_func
第二个教程来自here。
我的组合代码如下所示并产生错误。
#decorators.py
from functools import wraps
import logging
logging.basicConfig(level=logging.DEBUG)
def logged(level=logging.INFO, name=None, message=None):
'''
Dump function arguments to log file.
Optionally, change the logging level of the call, the name of the logger to
use and the specific message to log as well
'''
def decorate(func):
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = name if name else func.__module__
logger = logging.getLogger(fname)
logmsg = message if message else None
@wraps(func)
def wrapper(*args, **kwargs):
"""
Log arguments, including name, type and value
"""
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.log(level, " args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
if logmsg:
logger.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
从我的烧瓶应用程序中调用它是这样的:
@app.route("/hello/")
@app.route("/hello/<name>")
@api_decorators.logged
def hello(name=None):
s = "Hello"
if name:
s = "%s %s!" % (s, name)
else:
s = "%s %s!" % (s, "World!")
return s
产生的错误是
TypeError: decorate() got an unexpected keyword argument 'name'
整个堆栈跟踪是
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\flask\app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "C:\Python27\lib\site-packages\flask\app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "C:\Python27\lib\site-packages\flask\app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "C:\Python27\lib\site-packages\flask\app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Python27\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Python27\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
TypeError: decorate() got an unexpected keyword argument 'name'
如何修复组合代码以消除此错误?
答案 0 :(得分:2)
您需要实际调用logged
装饰器,即使您没有要传递的参数。
@api_decorators.logged()
def hello(name=None):
您的代码有三个功能:
logged
函数,它创建一个配置了传递给它的参数的装饰器。它返回:decorator
函数,它接受一个函数来装饰。它返回:所以你的代码应该这样调用:
logged()(hello)(name='something')
#Call 1 2 3, which calls hello inside it
但在您的代码中,它的调用方式如下:
logged(hello)(name='something')
#Call 1 2
decorator
函数并不期望name
参数,这是导致错误的原因。
您可以使用hack允许使用装饰器而无需先调用它。您需要检测何时使用装饰器而不进行调用。我认为会是这样的:
def logged(level=logging.INFO, name=None, message=None):
...
# At the bottom, replace the return with this
# If level is callable, that means logged is being called as a decorator
if callable(level):
f = level
level = logging.INFO
return decorator(f)
else:
return decorator
答案 1 :(得分:1)
您可以通过将签名修改为与此类似来执行此操作:
def logged(func=None, level=logging.INFO, name=None, message=None):
在这种情况下,您将删除您实施的decorate
功能并退出wrapper
功能:
def logged(func=None, level=logging.INFO, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = name if name else func.__name__
logger = logging.getLogger(fname)
logmsg = message if message else None
@wraps(func)
def wrapper(*args, **kwargs):
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.log(level, " args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
if logmsg:
logger.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
这利用了partial
方法。
利用您发布的应用程序代码(未修改)的示例结果。这不需要()
电话上的@api_decorators.logged
:
2014-10-21 15:53:08,756 - INFO - hello - args =>
2014-10-21 15:53:12,730 - INFO - hello - args => name=unicode<Andy>
第一个是拨打/hello/
,第二个是拨打/hello/Andy