views:

97

answers:

4

Is it possible to create a decorator which can be __init__'d with a set of arguments, then later have methods called with other arguments?

For instance:

from foo import MyDecorator

bar = MyDecorator(debug=True)

@bar.myfunc(a=100)
def spam():
    pass

@bar.myotherfunc(x=False)
def eggs():
    pass

If this is possible, can you provide a working example?

+3  A: 

Sure, a decorator is just a function which accepts a function and returns a function. There's no reason that function can't be (or, if you have arguments, can't be returned by) an instance method. Here's a really trivial example (because I'm not sure what exactly you'd be trying to do with this):

class MyDecorator(object):
    def __init__(self, debug):
        self.debug = debug
    def myfunc(self, a):
        def decorator(fn):
            def new_fn():
                if self.debug:
                    print a
                fn()
            return new_fn
        return decorator
    def myotherfunc(self, x):
        def decorator(fn):
            def new_fn():
                if self.debug:
                    print x
                fn()
            return new_fn
        return decorator

Like I said, I can't think of a use case for this off the top of my head. But I'm sure they're out there.

David Zaslavsky
First person to complain that I'm implying functions and instance methods are the same thing gets a pie in the face ;-)
David Zaslavsky
Well, a decorator has to be a `callable` that takes one `callable` and returns something - but usually that something should be callable too, else you'll end up confusing any readers. Do i get a pie anyways? ;-)
THC4k
How about a waffle?
David Zaslavsky
A: 

The property decorator is kind of like this. @property decorates a function and replaces it with an object that has getter, setter and deleter functions, which are also decorators.

This is a little more complex than the OP's example because there are two levels of decoration, but the principle is the same.

Dave Kirby
+4  A: 

You need another level of wrapping for this, using closures for example:

import functools

def say_when_called(what_to_say):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kw):
            print what_to_say
            return fn(*args, **kw)
        return wrapper
    return decorator

@say_when_called("spam")
def my_func(v):
    print v

my_func("eggs")

Output:

spam
eggs

(see http://codepad.org/uyJV56gk)

Note that I've used the functools.wraps here to make the decorated function look like the original. It's not functionally required, but a nice thing to do in case code reads the __name__ or __doc__ attributes of your function.

A class-based example:

class SayWhenCalledWrapper(object):

    def __init__(self, fn, what_to_say):
        self.fn = fn
        self.what_to_say = what_to_say

    def __call__(self, *args, **kw):
        print self.what_to_say
        return self.fn(*args, **kw)


class SayWhenCalled(object):

    def __init__(self, what_to_say):
        self.what_to_say = what_to_say

    def __call__(self, fn):
        return SayWhenCalledWrapper(fn, self.what_to_say)


@SayWhenCalled("spam")
def my_func(v):
    print v

my_func("eggs")

Output:

spam
eggs

(see http://codepad.org/6Y2XffDN)

huin
A: 

huin's answer is very good. His two options execute decorator code only when the decorated function is defined (that's not a criticism, as often that's exactly what you want). Here's an extension of his class-based approach, which also executes some code each time you call the function. This is the sort of thing you do, for example, when you are using decorators to ensure thread safety.

class MyInnerDecorator:
    def __init__( self, outer_decorator, *args, **kwargs ):
        self._outerDecorator = outer_decorator
        self._args = args
        self._kwargs = kwargs

    def __call__( self, f ):
        print "Decorating function\n"
        self._f = f
        return self.wrap


    def wrap( self, *args, **kwargs ):
        print "Calling decorated function"
        print "Debug is ",                          self._outerDecorator._debug
        print "Positional args to decorator: ",     self._args
        print "Keyword args to decorator: ",        self._kwargs
        print "Positional args to function call: ", args
        print "Keyword args to function call: ",    kwargs
        return self._f( *args, **kwargs )
        print "\n"



class MyDecorator:
    def __init__( self, debug ):
        self._debug = debug

    def myFunc( self, *args, **kwargs ):
        return MyInnerDecorator( self, "Wrapped by myFunc", *args, **kwargs )

    def myOtherFunc( self, *args, **kwargs ):
        return MyInnerDecorator( self, "Wrapped by myOtherFunc", *args, **kwargs )


bar = MyDecorator( debug=True )
@bar.myFunc( a=100 )
def spam( *args, **kwargs ):
    print "\nIn spam\n"

@bar.myOtherFunc( x=False )
def eggs( *args, **kwargs ):
    print "\nIn eggs\n"

spam( "penguin" )

eggs( "lumberjack" )

Which outputs this:

Decorating function

Decorating function

Calling decorated function
Debug is  True
Positional args to decorator:  ('Wrapped by myFunc',)
Keyword args to decorator:  {'a': 100}
Positional args to function call:  ('penguin',)
Keyword args to function call:  {}

In spam

Calling decorated function
Debug is  True
Positional args to decorator:  ('Wrapped by myOtherFunc',)
Keyword args to decorator:  {'x': False}
Positional args to function call:  ('lumberjack',)
Keyword args to function call:  {}

In eggs
Dan Menes