简体   繁体   中英

Decorator with configurable attributes got an unexpected keyword argument

I am attempting to combine two decorator tutorials into a single decorator that will log function arguments at a specified log level.

The first tutorial is from here and looks like this (and works as expected):

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

The second tutorial is from here .

My combined code looks like this and produces an error.

#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

It is being called from my flask application like this:

@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

The error that is produced is

TypeError: decorate() got an unexpected keyword argument 'name'

The entire stack trace is

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'

How can I fix the combined code to eliminate this error?

You need to actually call the logged decorator, even if you don't have a parameter to pass.

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

Your code has three functions:

  1. The logged function, which creates a decorator that is configured with the arguments passed to it. It returns:
  2. The inner decorator function, which takes a function to decorate. It returns:
  3. The wrapper function, which wraps the decorated function.

So your code should be called like this:

logged()(hello)(name='something')
#Call 1 2      3, which calls hello inside it

But in your code, it is called like this:

logged(hello)(name='something')
#Call 1      2

The decorator function doesn't expect the name argument, which is what causes the error.

You can use a hack allow the decorator to be used without calling it first. You need to detect when the decorator is used without being called. I think it would be something like this:

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

You can do this by modifying the signature to be similar to this:

def logged(func=None, level=logging.INFO, name=None, message=None):

In this case, you will remove the decorate function you implemented and leave the wrapper function:

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

This takes advantage of the partial method.

Example results utilizing your posted application code (unmodified). This doesn't require the () on the @api_decorators.logged call:

2014-10-21 15:53:08,756 - INFO - hello -  args =>
2014-10-21 15:53:12,730 - INFO - hello -  args => name=unicode<Andy>

The first one is a call to /hello/ and the second is a call to /hello/Andy

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM