WeizhongTu
WeizhongTu

Reputation: 6424

How to understand python decorator arguments pass

I try to understand python decorator

def dec(func):
    def wrap(*args,**kw):
        print args, kw
        return func(*args,**kw)
    return wrap

@dec
def myfunc(a=1,b=2,c=3):
    return a+b+c


>>> myfunc()
() {}
6
>>> myfunc(1,2,3)
(1, 2, 3) {}
6
>>> myfunc(1,2,c=5)
(1, 2) {'c': 5}
8
>>> 

When I run myfunc() args and kw are nothing, but when I run myfunc(1,2,3) and myfunc(1,2,c=5), args and kw were passed to dec function.

As I know,

@dec
def myfunc(...)

equals to myfunc = dec(myfunc) <-- no arguments were mentioned here.

How arguments were passed to wrap function in dec? How to understand these?

Upvotes: 2

Views: 252

Answers (3)

soulcheck
soulcheck

Reputation: 36777

Not sure if i understand correctly your problem, but the default values for myfunc arguments are known only to myfunc - your decorator has no knowledge of them, so it cannot print them.

That's why:

myfunc()

results in printing:

() {}

Both *args and **kw are empty for the decorator, but the decorated function will use the default values in this case.

In the second and third case you get the parameters printed, as they are explicitly passed to the decorated function:

 def wrap(*args,**kw): <- this is what is actually called when you invoke decorated function, no default values here
    print args, kw
    return func(*args,**kw) <- this is the function you decorate
    #if func has default parameter values, then those will be used 
    #when you don't pass them to the wrapper but only **inside** func
 return wrap

Edit: It looks like you're mistaking calling the decorated function with decorating the function:

myfunc = dec(myfunc) 

decorates myfunc using dec and is equivalent to

@dec
def myfunc(...)

On the other hand, after using either of them:

myfunc(whatever)

calls the wrap function defined in your decorator, which will in turn call the original myfunc

Upvotes: 3

Oleg Sklyar
Oleg Sklyar

Reputation: 10092

Decorators are function wrappers. They give back a function that wraps the original one into some pre- and post-processing code, but still need to call the original function (normally with the same argument as you would call it in absence of a decorator).

Python has two types of arguments, positional and keyword arguments (this has nothing to do with decorators, that's generic python basics). * is for positional (internally is a list), ** for keyword (dictionary) arguments. By specifying both you allow your decorator to accept all at all possible types of arguments and pass them through to the underlying function. The contract of the call is, however, still defined by your function. E.g. if it only takes keyword arguments it will fail when the decorator function passes through a positional argument.

In your particular example, you have some pre-processing code (i.e. code that will run before the original function is called). For example in this code you can print out arguments *args that your original function might fail to accept all together because it does not take any position arguments.

You do not necessarily have to pass through *args and **kwargs. In fact you can define a decorator which makes some decisions based on the arguments you pass in about what to provide to the original function as arguments, e.g.

def dec(fun):
    def wrap(*args, **kwargs):
        if 'a' in kwargs:
            return fun(kwargs[a])
        else:
            return fun(*args)
    return wrap

Upvotes: 1

user890167
user890167

Reputation:

Another way to think of it is by saying:

def wrapper(some_function):
    def _inner(*args, **kwargs):
        #do some work
        return func(*args, **kwargs)
    return _inner

@wrapper
def foo(a, b, c):
    return "Foo called, and I saw %d, %d, %d" % (a, b, c)

...you're getting a result which is roughly similar to the following:

def foo(a, b, c):
    #do some work
    return "Foo called, and I saw %d, %d, %d" % (a, b, c)

This isn't exactly right because the #do some work is occurring before the actual foo() call, but as an approximation this is what you're getting. For that reason, the wrapper can't really 'see' the default arguments for foo() if any exist. So a better way to think of it might be:

#always execute this code before calling...
def foo(a, b, c):
    return "Foo called and I saw %d, %d, %d" % (a, b, c)

So something really basic might look like this.

def wrap(f):
...     def _inner(*a, **k):
...             new_a = (ar*2 for ar in a)
...             new_k = {}
...             for k, v in k.iteritems():
...                     new_k[k] = v*2
...             return f(*new_a, **new_k)
...     return _inner
... 
>>> def foo(a=2, b=4, c=6):
...     return "%d, %d, %d" % (a, b, c)
... 
>>> foo()
'2, 4, 6'
>>> foo(1, 5, 7)
'1, 5, 7'
>>> foo = wrap(foo)    #actually wrapping it here
>>> foo()
'2, 4, 6'
>>> foo(3, 5, 6)
'6, 10, 12'
>>> foo(3, 5, c=7)
'6, 10, 14'
>>> 

Upvotes: 1

Related Questions