Decorator that can accept both init args and call args?

Is it possible to create a decorator that can be __init__

'd with a bunch of arguments and then 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 possible, can you provide a working example?

+2


a source to share


4 answers


Of course, a decorator is just a function that takes a function and returns a function. There is no reason a function cannot be (or, if you have arguments, cannot be returned) an instance method. Here's a really trivial example (because I'm not sure what exactly you are 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

      



As I said, I can't think of an option for this out of my head. But I'm sure they are there.

+3


a source


To do this, you need another layer of wrapping, using a closure, 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 used functools.wraps

here to make the decorated function look like the original. This is not functionally necessary, but a good thing to do if your code reads attributes __name__

or __doc__

your function.



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 )

+4


a source


the property decorator looks something like this. @property

decorates a function and replaces it with an object that has getter, setter and deleter functions that are decorators as well.

It's a bit more complicated than the OP's example because there are two levels of decoration, but the principle is the same.

0


a source


Huin's answer is very good. Its two variants execute decorator code only when a certain function is defined (this is not a criticism, how often this is exactly what you want). Here's an extension of his class-based approach, which also executes every code on every function call. This is what you do, for example, when you use decorators for 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

      

0


a source







All Articles