2

Is it possible to pass the decorated method with arguments to __init__ of an decorator?

A simple decorator and usage example

class Decorator(object):
    def __init__(self, *args):
        print args

    def __call__(self, func): # yep the method still have to be callable
        return func

@Decorator
def foo():
    pass

A decorator without arguments will pass the method as argument

$ python foo.py
(<function foo at 0x7fefd7ac1b90>,)

When I add arguments to the decorator

@Decorator(1, 2, 3)
def foo():
    pass

it results in

$ python test.py 
(1, 2, 3)

As you can see the method is now missing in the passed arguments.

Lucas
  • 3,376
  • 6
  • 31
  • 46
  • Try an example with `@Decorator()` and maybe it will click. – Platinum Azure Mar 27 '14 at 21:45
  • 2
    There is a great description [here](http://stackoverflow.com/a/1594484/674039) (one of the best answers on stack, in my opinion) – wim Mar 27 '14 at 21:45
  • 1
    `@Decorator` basically means `foo = Decorator(foo)`. Therefore `@Decorator(1, 2, 3)` means `foo = Decorator(1, 2, 3)(foo)`; `Decorator(1, 2, 3)` should *return a decorator*. – RemcoGerlich Mar 27 '14 at 21:48

2 Answers2

4

When we pass arguments to a decorator we need to create an additional function that accepts those arguments and then returns the actual decorator:

def decorator_creator(*args):
    class Decorator(object):
        def __init__(self, func):
            print args
            print func
            self.func = func
        def __call__(self):
            return self.func()
    return Decorator

@decorator_creator(1, 2, 3)
def foo():
    pass

Output:

(1, 2, 3)
<function foo at 0x0000000002EB9EB8>
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • 1
    Probably worth noting that for the vast majority of cases, implementing a decorator as a class, even if it needs arguments, is overkill, and arguably less clear code; an outter closure usually suffices, and makes for more readable code. Or at least define the decorator object outside the decorator function scope, then initialize and return an instance when the decorator is called. – Silas Ray Mar 27 '14 at 21:49
  • Just a minor observation, won't this code create a `Decorator` class every time the decorator is applied? – Paulo Bu Mar 27 '14 at 21:51
  • @PauloBu That is required, because we're using a free variable `args` in our class here. If we move the class outside of the function then the class won't be able to access `args`. – Ashwini Chaudhary Mar 27 '14 at 21:54
  • Unless you make `args` the arguments to the class' `__init__` and return an instance, which seems to make more sense here... – Silas Ray Mar 27 '14 at 21:55
  • @Lucas I accidentally removed the `__call__` method instead of fixing it. – Ashwini Chaudhary Mar 27 '14 at 22:04
  • @SilasRay If I return an instance from `decorator_creator` then it would call the `__call__` method of that instance during function creation time itself. – Ashwini Chaudhary Mar 27 '14 at 22:15
2

An alternative that doesn't need inner classes:

class decorator(object):
    def __init__(self, *args):
        # This creates the decorator
        self.args = args

    def __call__(self, func):
        # This applies the decorator
        self.func = func
        return self.call

    def call(self, *moreargs):
        # And this happens when the original function is called
        print self.args, self.func, moreargs
        return self.func()

@decorator(1, 2, 3)
def foo():
    pass

I've also made use of functools.partial(self.method, func) for decorators. Sometimes useful.

Eevee
  • 47,412
  • 11
  • 95
  • 127