@decorator syntax in Python is easy to abuse. After all, it's simply a syntactic sugar for:
obj = decorator(obj)
obj must be a function or a class but Python doesn't care about the output value that is then assigned to the same name. It may be, quite literally, anything assignable. A perfect place to get clever and show to your fellow programmers how cool you are!
Rule of thumb
This is of course very wrong, especially in Python, where the principle of least surprise is universally respected. When deciding if something can be implemented as a decorator my rule of thumb is that a decorator should not alter the semantics of its argument function.
Or, put without the use of the curse word "semantics", a decorated function is expected to be called in exactly the same way — arguments, the return value and general meaning — as if it wasn't decorated. This way the user can still see how to use the decorated function by looking only at its code (or docs) without hunting for the definition of the decorator.
I once saw a code where decorators were used as a sort of a weird DSL:
@text @user def create_post(user, text): backend.callCreatePost(user, text) ... create_post(request) # wait, "request"? wtf?
All the interface functions were called in a similar fashion with a dict-like argument from which it was possible to get everything they needed. The idea was to extract common actions, like getting a user object, into decorators and let the resulting function look "cleaner".
As you might expect, it was really hard to debug. And since all those decorators were inter-dependant on each other providing parameters in the correct places you would periodically fight problems like "we need this parameter before that one but can't do it because that other function already wants them the other way around". Much time was also spent on solving really interesting problems like introspecting the name of a n-th positional argument which wasn't even possible on Python 2.4 back then…
A simpler approach, while using more characters, would be nonetheless more readable:
def create_post(request): user = get_user(request) text = get_text(request) backend.callCreatePost(user, text)
Also keep in mind that when you apply a
@decorator to a function the function itself is effectively lost from the namespace and there's no (simple) way to call it in its original un-decorated form. This might be a problem, especially in a reusable library-style code because it's usually hard to anticipate at the time of writing all the ways in which it can be used in the future. Sometimes it's best to provide both the function and the decorator separately and let the user decide how to call them.