Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decorator with options

I have a module that has a function whose prototype is similar to that of the thread class.

def do(fn, argtuple=(), kwargdict={}, priority=0,
            block=False, timeout=0, callback=None, daemon=False)

    # do stuff

fn is a callable, and argtuple and kwargdict are positional and dictionary arguments that will be passed to the fn callable when it gets called.

I'm now trying to write a decorator for this, but I'm confused. I've never really had a very good grasp on decorators. Is there a way to make a decorator that I can set the options in the above such as timeout, but pass in argtuple and kwargdict when the function is called.

So for example:

@do(priority=2)
def decoratedTask(arg, dic=3):
    #do stuff

decoratedTask(72)

I'm confused how I would pass the runtime argument 72 into the decorated function. I'm thinking the decorator needs to be a class where the __call__ method returns the function call, but I'm unsure the syntax of how to pass in arguments like that.

Does this make sense?

like image 305
Falmarri Avatar asked Dec 20 '10 02:12

Falmarri


People also ask

Can decorator have 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.

Can decorators return values?

Decorator Functions with Decorator ArgumentsThe return value of the decorator function must be a function used to wrap the function to be decorated. That is, Python will take the returned function and call it at decoration time, passing the function to be decorated.

What are fancy decorators in Python?

These fancy symbols are called decorators. Python decorators represent a form of metaprogramming. This means they have the ability to modify another part of the program while the code is running. In simple terminology, they are functions that take other functions as arguments.


2 Answers

What the decorator does is that it takes the function as an argument and also returns a function, typically a new function that is created in the decorator.

That new function needs to take the same parameters as the function you decorate, and it also needs to call the original function.

Now, when you have a decorator with an argument, there is an extra level going on. That decorator should take the argument, and return a decorator. The function you use is in fact not a decorator, but a decoratormaker!

Here is an example:

>>> def mydeco(count):
...     def multipass(fn):
...         def caller(*args, **kw):
...             return [fn(*args, **kw) for x in range(count)]
...         return caller
...     return multipass
... 
>>> @mydeco(5)
... def printer(text):
...     print(text)
... 
>>> printer("Yabbadabbadoo!")
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
Yabbadabbadoo!
[None, None, None, None, None]

You'll see examples of these decoratormakers implemented as classes. That how I prefer it too (although I usually end up not having a decorator at all anyway), but a function in a function in a function works. :)

like image 179
Lennart Regebro Avatar answered Oct 01 '22 15:10

Lennart Regebro


That's not quite how the decorator syntax works. When you write @do(priority=2), Python will evaluate do(priority=2) and use the result of that call as the decorator. It's shorthand for

decorator=do(priority=2)
@decorator

So you actually want to make do a decorator factory: you want it to take all the positional arguments and return a decorator.

def do(args=(), kwargs={}, ...):
    def _decorator(fn):
        def newfn(*args, **kwargs):
            return fn(*args, **kwargs)
        return newfn
    return _decorator

Notice that there are actually three functions here!

  • do is the function that we call do get our decorator (so e.g. do(priority=2) is an example decorator)
  • _decorator is the actual decorator returned, which depends on the arguments to do
  • Since a decorator takes a function as input and returns a function as output, we need to define the function that the decorator returns. newfn is that function.

Example:

>>> def rename(name):
...     def _decorator(fn):
...             def renamed(*args, **kwargs):
...                     return fn(*args, **kwargs)
...             renamed.__name__ = name
...             return renamed
...     return _decorator
...
>>> @rename('I like omelettes in the morning.')
... def foo():
...     return 'bar'
...
>>> foo()
'bar'
>>> foo.__name__
'I like omelettes in the morning.'

or equivalently

>>> omeletter = rename('I like omelettes in the morning.')
>>> @omeletter
... def foo():
...     return 'bar'
...
>>> foo()
'bar'
>>> foo.__name__
'I like omelettes in the morning.'

By the way, take care with the mutable default arguments () and {}; I'm sure you know the perils!

like image 23
Katriel Avatar answered Oct 01 '22 15:10

Katriel