Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: shortcut for writing decorators which accept arguments?

Does the Python standard library have a shortcut for writing decorators which accept arguments?

For example, if I want to write a decorator like with_timeout(timeout):

@with_timeout(10.0)
def cook_eggs(eggs):
    while not eggs.are_done():
        eggs.cook()

I have to write something like:

def with_timeout(timeout):
    _func = [None]
    def with_timeout_helper(*args, **kwargs):
        with Timeout(timeout):
            return _func[0](*args, **kwargs)
    def with_timeout_return(f):
        return functools.wraps(f)(with_timeout_helper)
    return with_timeout_return

But that's awfully verbose. Is there a shortcut which makes decorators which accept arguments easier to write?

Note: I realize that it's also possible to use three nested functions to implement decorators with arguments… But that feels just a bit suboptimal too.

For example, possibly something like a @decorator_with_arguments function:

@decorator_with_arguments
def timeout(f, timeout):
    @functools.wraps(f)
    def timeout_helper(*args, **kwargs):
        with Timeout(timeout):
            return f(*args, **kwargs)
    return timeout_helper
like image 340
David Wolever Avatar asked May 16 '12 01:05

David Wolever


People also ask

Can decorators accept arguments?

The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.

What does @wraps do in Python?

wraps() is a decorator that is applied to the wrapper function of a decorator. It updates the wrapper function to look like wrapped function by copying attributes such as __name__, __doc__ (the docstring), etc. Parameters: wrapped: The function name that is to be decorated by wrapper function.

What is a Declarator in Python?

A decorator in Python is a function that takes another function as its argument, and returns yet another function . Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.


2 Answers

I tend to write my decorators as classes to be honest

class TestWithArgs(object):
    def __init__(self, *deco_args, **deco_kwargs):
        self.deco_args = deco_args
        self.deco_kwargs = deco_kwargs
    def __call__(self, func):
        def _wrap(self, *args, **kwargs):
            print "Blah blah blah"
            return func(*args, **kwargs)
        return _wrap

Its nothing if not slightly clearer

like image 195
Jakob Bowyer Avatar answered Oct 16 '22 19:10

Jakob Bowyer


I know you said it feels suboptimal but I still feel that using three nested models is the cleanest solution. The inner two functions are just the 'normal' way of defining a decorator for a function that takes arguments (see example in python's docs for @wraps). The outer one is really just a function that takes and argument and returns a decorator.

def with_timeout(timeout):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            with Timeout(timeout):
                return f(*args, **kwargs)
        return wrapper
    return decorator
like image 4
Nathan Avatar answered Oct 16 '22 19:10

Nathan