Why arn't the following two scripts equivalent?
(Taken from another question: Understanding Python Decorators)
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print hello() ## returns <b><i>hello world</i></b>
and with a decorated decorator:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
@makebold
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeitalic
def hello():
return "hello world"
print hello() ## TypeError: wrapped() takes no arguments (1 given)
Why do I want to know? I've written a retry
decorator to catch MySQLdb exceptions - if the exception is transient (e.g. Timeout) it will re-call the function after sleeping a bit.
I've also got a modifies_db
decorator which takes care of some cache-related housekeeping. modifies_db
is decorated with retry
, so I assumed that all functions decorated with modifies_db
would also retry implicitly. Where did I go wrong?
So, here in this post, we are going to learn about Decorator Chaining. Chaining decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together.
Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.
Python decorators are a powerful concept that allow you to "wrap" a function with another function. The idea of a decorator is to abstract away something that you want a function or class to do, besides its normal responsibility. This can be helpful for many reasons such as code reuse, and sticking to curlys law.
Not using async also gives the advantage of being able to use the same decorator in normal functions as well. The only advantage of having async with a single return await is to make it clearly documented the function must be awaited, but that doesn't matter in a decorator wrapper.
The problem with the second example is that
@makebold
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
is trying to decorate makeitalic
, the decorator, and not wrapped
, the function it returns.
You can do what I think you intend with something like this:
def makeitalic(fn):
@makebold
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
Here makeitalic
uses makebold
to decorate wrapped
.
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