I am learning a bit about decorators from a great tutorial on thecodeship but have found myself rather confused by one example.
First a simple example followed by an explanation is given for what a decorator is.
def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
my_get_text = p_decorate(get_text)
print my_get_text("John")
Now this makes sense to me. A decorator is simply a wrapper to a function. And in this guys explanation he says a decorator is a function that takes another function as an argument, generates a new function, and returns the generated function to be used anywhere.
And now the equivalent to the above code is:
def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper
@p_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
print get_text("John")
I believe I understand the way a decorator is initialized when given no arguments. Correct me if I am wrong.
get_text
and because p_decorate
returns a function func_wrapper
, we end up with the true statement get_text = func_wrapper
.What is important to me is the first code block equivalent, because I see and understand how the decorator is behaving.
What very much confuses me is the following code:
def tags(tag_name):
def tags_decorator(func):
def func_wrapper(name):
return "<{0}>{1}</{0}>".format(tag_name, func(name))
return func_wrapper
return tags_decorator
@tags("p")
def get_text(name):
return "Hello "+name
print get_text("John")
Again, correct me if I'm wrong but this is my understanding.
tags_decorator
assumes that the parameter that will be passed in is the function
being decorated, get_text
.It might be helpful for me to see the equivalent block of code in "non-decorator" form but I can't seem to wrap my head around what that would begin to look like. I also don't comprehend why tags_decorator
and func_wrapper
are both returned. What is the purpose of returning two different functions if a decorator only needs to return 1 function to wrap get_text
.
As a side note, it really comes down to the following.
Within limits, everything after the @
is executed to produce a decorator. In your first example, what follows after the @
is just a name:
@p_decorate
so Python looks up p_decorate
and calls it with the decorated function as an argument:
get_text = p_decorate(get_text)
(oversimplified a bit, get_text
is not initially bound to the original function, but you got the gist already).
In your second example, the decorator expression is a little more involved, it includes a call:
@tags("p")
so Python uses tags("p")
to find the decorator. In the end this is what then is executed:
get_text = tags("p")(get_text)
The output of tags("p")
is the decorator here! I call the tags
function itself a decorator factory, it produces a decorator when called. When you call tags()
, it returns tags_decorator()
. That's the real decorator here.
You could instead remove the decorator function and hardcode the "p"
value and use that directly:
def tags_decorator_p(func):
def func_wrapper(name):
return "<{0}>{1}</{0}>".format("p", func(name))
return func_wrapper
@tags_decorator_p
def get_text(name):
# ...
but then you'd have to create separate decorators for each possible value of the argument to tags()
. That's the value of a decorator factory, you get to add parameters to the decorator and alter how your function is decorated.
A decorator factory can take any number of arguments; it is just a function you call to produce a decorator. The decorator itself can only accept one argument, the function-to-decorate.
I said, within limits at the start of my answer for a reason; the syntax for the expression following the @
only allows a dotted name (foo.bar.baz
, so attribute access) and a call ((arg1, arg2, keyword=arg3)
). See the reference documentation. The original PEP 318 states:
The decorator statement is limited in what it can accept -- arbitrary expressions will not work. Guido preferred this because of a gut feeling [17] .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With