Steve Garrett
Steve Garrett

Reputation: 23

When is a function decorated, and when isn't a function decorated?

I have a decorator that records the functions present in my script:

registry=[]

def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func

I then have a series of decorated functions:

@register
def func1():
    print('running f1')

@register 
def func2():
    print('running f2')

This works, after running the script, print(registry) returns:

[<function func1 at 0x0000000008433950>, <function func2 at 0x0000000008F06AE8>]

However calling the functions individually, for example:

func1()

Returns only 'running f1': just the function, without the decoration.

I was expecting it to return something like 'running register( func1) \n running func1'.

So my question is, when you have a decorated function and call it; when will it call the function in isolation and when will it call the decorated function?

Thanks very much.

Upvotes: 2

Views: 117

Answers (2)

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

What we call a "decorator" is just a higher order function, and the @decorator syntax nothing more than syntactic sugar so this:

@decorate
def func():
    pass

is strictly equivalent to

def func():
    pass

func = decorate(func)

As mentionned by Guillaume Deslandes, if this code is at your module or script's top-level, the decorator is only invoked when the module or script is first loaded by the runtime.

In your case, the decorator function register returns it's argument (the function it's applied to) unchanged, so calling the "decorated" function will work exactly as if it never had been decorated.

If you want to modify the decorated function in any way (by executing code before and or after the original function or whatever), you have to return a new function that will "replace" the original (but - usually - keeping a reference to the original function so this new "wrapper" function can still call the original), which is usually done using the fact that Python functions are closure:

def decorate(fun):
   def wrapper(*args, **kw):
       res = fun(*args, **kw)
       print("function {} called with *{}, *{} returned {}".format(fun, args, kw, res)
       return res

   return wrapper



@decorate
def fun(a):
    return a * 2

Upvotes: 1

guillaume.deslandes
guillaume.deslandes

Reputation: 1349

Your register (decorator) function is only run once when the code is interpreted.

If you want to alter the function behaviour, try something like:

def register(func):
    registry.append(func)
    print('adding register(%s)' % func)

    def wrap(*args, **kwargs):
        print('running register(%s)' % func)
        return func(*args, **kwargs)

    return wrap

The first print is done once, the second one before each call. Adding the arguments will make your decorator more transparent.

Upvotes: 3

Related Questions