4

I'm trying to access all arguments passed to a function in the decorator, including optional arguments. Consider this example:

def decorator(fn):
    def wrapper(*args, **kwargs):
        print 'in wrapper', args, kwargs
        fn(*args, **kwargs)
    return wrapper

@decorator
def myFn(arg1, arg2, arg3=None):
    print 'in myFn', arg1, arg2, arg3

myFn(1,2)
myFn(1,2,3)

If I run this, I'll get:

in wrapper (1, 2) {}
in myFn 1 2 None
in wrapper (1, 2, 3) {}
in myFn 1 2 3

In the first run, since I don't specify 3 arguments, arg3 is defined as None for the purposes of myFn. But the fact that arg3 == None is not available inside of decorator, either in args or kwargs. If I explicitly pass it to myFn, it'll show up inside the decorator, but if I use the default value, it's nowhere to be found.

Why is this? And how can it be fixed?

austin1howard
  • 4,815
  • 3
  • 20
  • 23
  • Hm..Basically, using the `@decorator` syntax is like assigning `myFn = decorator(myFn)`. So yeah, the fact that you set `arg3=None` doesn't exist in the decorator. Why not just define wrapper with the same arguments as `myFn`? – aIKid Feb 25 '14 at 03:57
  • The end goal for this is to have the decorator look up that optional argument (which is a plain text name) and return an id, for use in myFn. But I'd like to be able to apply it to a large number of functions which all would have different call signatures. – austin1howard Feb 25 '14 at 04:01

2 Answers2

7

This is quite normal and cannot be "fixed"...

The decorator wrapper intercepts the arguments and keywords that are passed to the function: in other words, those passed by the caller of the function to the function itself.

arg3=None is a default argument defined at the function scope. It cannot be intercepted before the function is actually called (directly or via the wrapper) because it does not exist at that point.

However, the default values are stored in the function object:

def fn(arg1,arg2,arg3=None):
    pass

fn.func_defaults
-> (None,)

And you can use the following Get a function argument's default value? to map defaults to arguments... so I suppose the decorator could go to extreme lengths to print both passed and defaulted arguments. So I suppose my very first statement is not 100% correct :)

Community
  • 1
  • 1
isedev
  • 18,848
  • 3
  • 60
  • 59
  • I see. So basically the arg3 variable isn't defined until fn itself is actually called, inside wrapper. Great explanation. – austin1howard Feb 25 '14 at 04:21
4

You can use the decorator package to preserve the function signature:

from decorator import decorator

def decorate(f):
    def wrapper(f, *args, **kwargs):
        print 'in wrapper', args, kwargs
        return f(*args, **kwargs)
    return decorator(wrapper, f)

@decorate
def myFn(arg1, arg2, arg3=None):
    print 'in myFn', arg1, arg2, arg3

myFn(1, 2)
myFn(1, 2, 3)
myFn(1, 2, arg3=10)

Output:

in wrapper (1, 2, None) {}
in myFn 1 2 None
in wrapper (1, 2, 3) {}
in myFn 1 2 3
in wrapper (1, 2, 10) {}
in myFn 1 2 10
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • I suppose `decorator.decorator` goes to the extreme lengths I mentioned in my answer? Or is there some other trick? – isedev Feb 25 '14 at 07:38