Question

References

Before writing this question, I have referred some of the following interesting questions and feel that this scenario is not explained/covered:

I have a function as follows:

def simple_function():
    print 'this is a simple_function'

And, I have the following class with some methods

class Test:

    def Test_method_1(self, func, args=None, kwargs=None):
        print 'this is Test::Test_method_1'
        <execute some instructions here>

    def Test_method_2(self):
        print 'this is Test::Test_method_2'

I am using Test_method_1 and simple_function as follows:

t = Test()
t.Test_method_1(simple_function)

Say if the simple_function takes arbitrary arguments, then the Test_method_1 is called as follows:

def simple_function(a, b, c, d=f, g=h):
    print 'this is a simple_function'

t = Test()
t.Test_method_1(simple_function, args=[a, b, c], kwargs={d:f, g:h})

Now, I want to use Test_method_2 as a decorator version of Test_method_1. So the simple_function definition can be written as follows:

t = Test()

@t.Test_method_2
def simple_function():
    print 'this is a simple_function'

Note: the Test_method_2 will call Test_method_1 with appropriate arguments.

Now the questions:

  • 1 - Is this possible?
  • 2 - If possible, how do the name of the decorated function (here simple_function) will be passed as a argument to Test_method_2 (including the self)?
  • 3 - [Here is the killer question, I believe] I want to pass arbitrary arguments to both Test_method_2 (the decorator) and simple_function (the function being decorated) - *args and **kwargs - How should the Test_method_2 be defined to receive arguments (including the self)?

The usage of Test_method_2 as a decorator for simple_function with arbitrary arguments for both is as follows:

t = Test()
@t.Test_method_2(a, b, c, d, e=f, g=h)
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'
Was it helpful?

Solution

Sure it's possible. All the @decorator syntax does is define the function that follows, then call the decorator, passing in the function that followed and replacing the reference to that function with whatever was returned.

So the following:

@foo
def bar():
    pass

is translated to:

def bar():
    pass
bar = foo(bar)

This means that your t.Test_method_2() method must expect one argument, the to-be-decorated function, and return something callable:

import functools

def Test_method_2(self, func):
    @functools.wraps(func)
    def wrapper(self, *args, **kw):
        print 'Wrapped function name:', func.__name__
        return func(*args, **kw)
    return wrapper

would be the minimal decorator that returns a wrapper function, and prints the name of the wrapped function when called. It doesn't matter what the argument is called; I used func here but it can be any legal python identifier.

The self is the standard part of the method signature. Because you are referencing Test_method_2 on an instance t, Python automatically takes care of the self parameter for you as it does with all methods.

Anything after @ is just an expression. So if you use the syntax:

@t.Test_method_2(a, b, c, d, e=f, g=h)

then Test_method_2() should instead return a decorator function. One extra level of scope nesting should do that:

def Test_method_2(self, *args, **kw):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*wrapperargs, **wrapperkw):
            fargs = args + wrapperargs
            fkw = dict(kw)
            fkw.update(wrapperkw)
            return func(*fargs, **fkw)
        return wrapper
    return decorator

Deconstructing this:

@t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs')
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'

The part after the @, t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs') returns the nested function decorator:

@decorator
def simple_function(x, y, z, n=m):
    print 'this is a simple_function'

which python then turns into:

simple_function = decorator(simple_function)

and decorator(func) returns wrapper(*wrapperargs, **wrapperkw).

Calling simple_function(1, 2, foo='bar') then results in a call to wrapper(1, 2, foo='bar') which calls the original simple_function() with fargs = [5, 6, 7, 8, 1, 2] and fkw = {'baz': 'spam', 'ham': 'eggs', 'foo': 'bar'} passed in as positional and keyword arguments.

The class decorator pattern you see in the linked questions works in a similar way; the expression after the @ returns something that is called; instead of a nested function a class instance is being created instead. It's just two different approaches to storing state for the decorator to use.

The nested functions is a little more compact to write, while using a class gives you more introspection options than a nested scope.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top