具有可配置属性的Decorator获得了意外的关键字参数

时间:2014-10-21 14:30:18

标签: python python-2.7 flask decorator

我正在尝试将两个装饰器教程组合到一个装饰器中,该装饰器将在指定的日志级别记录函数参数。

第一个教程来自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'

如何修复组合代码以消除此错误?

2 个答案:

答案 0 :(得分:2)

您需要实际调用logged装饰器,即使您没有要传递的参数。

@api_decorators.logged()
def hello(name=None):

您的代码有三个功能:

  1. logged函数,它创建一个配置了传递给它的参数的装饰器。它返回:
  2. 内部decorator函数,它接受一个函数来装饰。它返回:
  3. 包装函数,它包装了装饰函数。
  4. 所以你的代码应该这样调用:

    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