Reputation: 1840
I'm trying to understand in the following code, how it is that arguments passed to the decorated function seem to be passed to the argument in the wrapper function:
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
def p_decorate(func):
def func_wrapper(*args, **kwargs):
return "<p>{0}</p>".format(func(*args, **kwargs))
return func_wrapper
my_get_text = p_decorate(get_text)
print my_get_text("John")
# <p>Outputs lorem ipsum, John dolor sit amet</p>
From trying to Google, I've gathered that the inner function func_wrapper()
has access to variables or objects in the enclosing scope through closure (I think I'm understanding this correctly anyway).
What I'm not understanding is how, exactly, the value of the argument name
passed to get_text(name)
is accessed by (or given to or assigned to) *args
or **kwargs
in the inner function func_wrapper()
.
I think I'm correct in understanding that the entire get_text(name)
function and its argument name
is passed to p_decorate()
and so is available in the scope within p_decorate()
— but how are the arguments passed to func_wrapper()
allowed to access the argument passed to get_text(name)
? What is the process or methods involved that allow that to happen?
Upvotes: 1
Views: 1023
Reputation: 251408
When you do my_get_text = p_decorate(get_text)
, you set my_get_text
to the result of calling p_decorate(get_text)
. The result of calling p_decorate
is your func_wrapper
. So when you call my_get_text
, you are effectively calling func_wrapper
.
Then look at what func_wrapper
does. It accepts any arguments (*args
and *kwargs
) and passes them to func
. Since func
was set to get_text
when you called p_decorate
, this calls get_text
with the same arguments that my_get_text
was called with.
You are right that there is a closure, but the closure doesn't really have anything to do with how the arguments in the call my_get_text("John")
are passed. The role of the closure is to ensure that the value of func
(namely, get_text
) is "saved" in func_wrapper
, so that the returned wrapper "knows" which function it is wrapping. But once the wrapped function is created, the actual argument-passing that occurs when you call it is just normal argument-passing. You call one function with arguments, and that function calls another function with the same arguments. It is no different than this:
def foo(x, y):
return x+y
def bar(x, y):
return foo(x, y)
If you now call bar
, it calls foo
. foo
is called with the same arguments because bar
calls it with the same arguments bar
was called with. Likewise, in your example get_text
gets the arguments because func_wrapper
calls get_text
with the same arguments func_wrapper
was called with.
Upvotes: 3
Reputation: 6331
Two things to note:
Variable defined in function defenition is defined in this functions local scope, which is an enclosing scope for any inner functions and therefore this variable is available in these inner functions. And function name (func
in your code) is just variable referencing function object.
*args
syntax in function defenition says "collect all unmatched positional arguments as a tuple and give this tuple the name args
", **kwargs
says "collect all unmatched keyword arguments as a dictionary and give this dictionary the name kwargs
". args
and kwargs
are just a convention (so as self
in classes) and you can use any name in this place, though you shouldn't. In function call the same syntax *
and **
does the opposite - it breaks a tuple and dictionary respectively in individual values and key=value pairs.
Now your code:
def get_text(name):
#name is here in local scope and it's just a positional argument
#if you've used get_text(*args, **kwargs) you could refer to 'name' as 'args[0]'
return "lorem ipsum, {0} dolor sit amet".format(name)
def p_decorate(func):
#'func' is saved here in local scope
#here 'func' will reference 'get_text' from your example
def func_wrapper(*args, **kwargs):
#'args' and 'kwargs' are here in local scope and
#they represent all arguments passed to decorated function
#regarding your example here will be 'arg[0]="John"'
return "<p>{0}</p>".format(func(*args, **kwargs))
#in the line above in function you basicly break 'args' and 'kwargs'
#into pieces and pass them to func as separate arguments
#regarding your example you basicly call 'func("John")
#where "John" is in 'arg[0]' and considering 'func' reference
#in your example it's basicly 'get_text(name)'
return func_wrapper
#line above returns function object which will be assigned
#to some name as result of decorator call, i.e. 'my_get_text'
my_get_text = p_decorate(get_text)
#effectively it says "set 'my_get_text' to reference 'func_wrapper'
#with argument 'func'='get_text'"
print my_get_text("John")
#effectively is says "call function referenced by 'my_get_text'
#which is 'func_wrapper' with argument 'John' is equal
#to call 'func_wrapper("John")'"
# <p>Outputs lorem ipsum, John dolor sit amet</p>
Using *args
and **kwargs
makes decorator more universal. In your example, if you know that only one argument is used you could write something like:
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
def p_decorate(func):
def func_wrapper(single_argument):
return "<p>{0}</p>".format(func(single_argument))
return func_wrapper
my_get_text = p_decorate(get_text)
print my_get_text("John")
# <p>Outputs lorem ipsum, John dolor sit amet</p>
Hope this will make it clearer to understand.
And regardin decorator syntax mentioned in comments to your question, usin @
is just syntactic sugar so:
def p_devorate(func):
...
@p_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
is the same as:
def p_devorate(func):
...
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
get_text = p_decorate(get_text)
#redefining the name of original function to pointed to wrappe function instead
Upvotes: 1
Reputation: 2274
When you call...
my_get_text = p_decorate(get_text)
...function p_decorate()
is executed with func = get_text
. It defines a new function func_wrapper()
which thus has access to func
as it was set at the time of function definition.
So, the return value of p_decorate()
is a newly minted function with signature of func_wrapper(*args, **kwargs)
which also conveniently has access to func
variable. Note that calling p_decorate()
again will create a different func_wrapper()
function with a different func
variable.
Now, when you call this newly created function:
my_get_text("John")
You're essentially calling the equivalent of:
def func_wrapper(*args, **kwargs):
func = get_text
# ...
Sunce you're calling it with a single positional argument, it is equivalent to args = ("John",), kwargs = {}
.
Upvotes: 1