带可配置属性的装饰器出现了意外的关键字参数
我正在尝试把两个装饰器的教程合并成一个,这个新的装饰器可以在指定的日志级别下记录函数的参数。
第一个教程可以在这里找到,内容如下(并且运行效果正常):
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
第二个教程来自这里。
我合并后的代码如下,但出现了错误。
#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
这个代码是从我的flask应用中这样调用的:
@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'
我该如何修复合并后的代码,以消除这个错误呢?
2 个回答
4
你需要实际调用一下 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
这个参数,这就是导致错误的原因。
你可以用一个小技巧让装饰器在没有被调用的情况下也能使用。你需要检测装饰器是否在没有被调用的情况下被使用。我觉得可以这样做:
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
你可以通过修改函数的签名,让它看起来像这样:
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