It is common that features dubbed ‘syntactic sugar’ are often fostering novel approaches to programming problems. Python’s decorators are no different here, and this was a topic I touched upon before. Today I’d like to discuss few quirks which are, unfortunately, adding to their complexity in a way that often doesn’t feel necessary.
Let’s start with something easy. Pretend that we have a simple decorator named @trace
, which logs every call of the function it is applied to:
An implementation of such decorator is relatively trivial, as it wraps the decorated function directly. One of the possible variants can be seen below:
That’s pretty cool for starters, but let’s say we want some calls to stand out in the logging output. Perhaps there are functions that we are more interested in than the rest. In other words, we’d like to adjust the priority of log messages that are generated by @trace
:
This seemingly small change is actually mandating massive conceptual leap in what our decorator really does. It becomes apparent when we de-sugar the @decorator
syntax and look at the plumbing underneath:
Introduction of parameters requires adding a new level of indirection, because it’s the return value of trace(level=logging.INFO)
that does the actual decorating (i.e. transforming given function into another). This might not be obvious at first glance and admittedly, a notion of function that returns a function which takes some other function in order to output a final function might be – ahem – slightly confusing ;-)
But wait! There is just one more thing… When we added the level
argument, we not necessarily wanted to lose the ability to invoke @trace
without it. Yes, it is still possible – but the syntax is rather awkward:
That’s expected – trace
only returns the actual decorator now – but at least slightly annoying. Can we get our pretty syntax back while maintaining the added flexibility of specifying custom arguments? Better yet: can we make @trace
, @trace()
and @trace(level)
all work at the same time?…
Looks like tough call, but fortunately the answer is positive. Before we delve into details, though, let’s step back and try to somewhat improve the way we are writing our decorators.