I recently started experimenting with Python decorators (and higher-order functions) because it looked like they might make my Django unit tests more concise. e.g., instead of writing:
def visit1():
login()
do_stuff()
logout()
I could instead do
@handle_login
def visit1():
do_stuff()
However, after some experimenting, I have found that decorators are not as simple as I had hoped. First, I was confused by the different decorator syntax I found in different examples, until I learned that decorators behave very differently when they take arguments. Then I tried decorating a method, and eventually learned that it wasn’t working because I first have to turn my decorator into a descriptor by adding a __get__ method. During this whole process I’ve ended up confused more than a few times and still find that debugging this “decorated” code is more complicated than it normally is for Python. I’m now re-evaluating whether I really need decorators in my code, since my initial motivation was to save a bit of typing, not because there was anything that really required higher-order functions.
So my question is: should decorators be used liberally or sparingly? Is it ever more Pythonic to avoid using them?
Decorators are fine in their place and definitely not to be avoided — when appropriate;-). I see your question as meaning essentially “OK so when are they appropriate”?
Adding some prefix and/or postfix code around some but not all methods of some classes is a good example. Were it all methods, a class decorator to wrap all methods would be better than repeating
@thisonetooendlessly;-). If it’s once in a blue moon then it’s not worth refactoring out to wrappers (decorators or otherwise). In the middle, there’s a large ground where decorators are quite suitable indeed.It boils down to one of the golden rules of programming — DRY, for Don’t Repeat Yourself. When you see your code becoming repetitious, you should refactor the repetition out — and decorators are an excellent tool for that, although they’re far from the only one (auxiliary methods and functions, custom metaclasses, generators and other iterators, context managers… many of the features we added to Python over the last few years can best be thought of as DRY-helpers, easier and smoother ways to factor out this or that frequent form of repetition!).
If there’s no repetition, there’s no real call for refactoring, hence (in particular) no real need for decorators — in such cases, YAGNI (Y’Ain’t Gonna Need It) can trump DRY;-).