Reputation: 679
I'm learning about Python decorators and I have some difficulties understanding how passing arguments to the decorated function works. I'm not talking about passing arguments to the decorator itself but of the case where the original function takes additional arguments. For instance, I'm trying to understand the following simple example (credits for the code snippet goes to this answer):
def decorator(fn):
def wrapper(arg1, arg2):
print("I got args! Look: {0}, {1}".format(arg1, arg2))
fn(arg1, arg2)
return wrapper
@decorator
def print_full_name(first_name, last_name):
print("My name is {0} {1}".format(first_name, last_name))
print_full_name("John", "Doe")
# outputs:
# I got args! Look: John Doe
# My name is John Doe
So, if I intentionally break the code by removing the arguments arg1
and arg2
of the wrapper, I get the following error: TypeError: wrapper() takes 0 positional arguments but 2 were given
. It's strange to me that the wrapper is expecting 2 arguments when it's defined without any arguments inside the parentheses. Also, is it unclear to me how the first_name
and last_name
argument values are "mapped" to the arguments of the wrapper function. We only pass those arguments to the print_full_name
function but it seems they also get passed to the wrapper
function. Maybe this has to do with the order in which things are run but this is not clear for me.
I know they are a lot of great answers here regarding this topic but I could not find one that clearly explains this specific part. Any help would be greatly appreciated.
Upvotes: 0
Views: 54
Reputation: 1931
You should break down the calls that happen.
So, you have:
def decorator(fn):
def wrapper(arg1, arg2):
print("I got args! Look: {0}, {1}".format(arg1, arg2))
fn(arg1, arg2)
return wrapper
@decorator
def print_full_name(first_name, last_name):
print("My name is {0} {1}".format(first_name, last_name))
print_full_name("John", "Doe")
# outputs:
# I got args! Look: John Doe
# My name is John Doe
Because you have decorated the print_full_name
with the decorator
, then when you do
print_full_name("John", "Doe")
first the decorator decorator
is called with argument print_full_name
, as in:
decorator(print_full_name)("John", "Doe")
The decorator(print_full_name)
is defined as
def decorator(fn):
def wrapper(...):
...
...
return wrapper
and returns the wrapper
. Thus, the above turns out to be:
wrapper("John", "Doe")
Then, because the wrapper
is defined as
def wrapper(arg1, arg2)
in the body of the wrapper
you have that arg1="John"
and arg2="Doe"
.
Finally, because the wrapper
is defined inside the decorator
, it knows what the fn
is, where fn=print_full_name
.
And that is how it works.
Upvotes: 1
Reputation: 530970
In short: passing arguments is exactly the same; you just need to be aware of which function you are actually calling.
Decorator syntax is just function application in disguise. Without it, you would just write
def decorator(fn):
def wrapper(arg1, arg2):
print("I got args! Look: {0}, {1}".format(arg1, arg2))
fn(arg1, arg2)
return wrapper
def print_full_name(first_name, last_name):
print("My name is {0} {1}".format(first_name, last_name))
print_full_name = decorator(print_full_name)
So now, print_full_name
does not refer to the original function that prints the sentence. It refers to the function returned by decorator
. The original function is not lost; the new function is a closure over the name fn
, which is a reference to the original function.
Upvotes: 1