It can be easy to succumb to the sheer mystery or python decorators. Even if you use them, you may not understand how they work. Let’s fix that.
First things first: a decorator is just a function.
In my opinion, this is an important thing to let soak in. Because, as developers, we know functions intimately; functions are not mysterious; we understand functions.
class Teacher: @property # <-- function def name(self): return self._name @classmethod # <-- function def has_pension(cls): return True @contextlib.contextmanager # <- function def with_context(): ...
So what is special about these function, then? What makes them decorators?
A decorator is a function that takes a function, and returns a function.
So that sounds simple enough, let’s try it!
# let's just define a function that fits # the above description def my_decorator(func): return func # so does that make it a decorator? @my_decorator def say_hello(name): print("hello, " + name) say_hello("world") # hello, world
It works! I mean, the decorator clearly has no effect on the running code, but it also did not create errors. So that proves the definition at least, but what exactly happened? Let’s add some print statments to find out:
print("defore decorator") def my_decorator(func): print("decorator called") print(func) return func print("after decorator") print("before function") @my_decorator def say_hello(name): print("hello, " + name) print("after function") say_hello("world") # defore decorator # after decorator # before function # decorator called # <function say_hello at 0x7f8792cd8dd0> # after function # hello, world
Interesting, so what we are learning here is that python is calling the decorator with the function as it’s being defined. So what happens to the return value? Can we return whatever we want?
def my_decorator(func): return None @my_decorator def say_hello(name): print("hello, " + name) print(say_hello) say_hello("world") # None # Traceback (most recent call last): # File "/home/rbottriell/work/ryanbottriell.com/test.py", line 11, in <module> # say_hello("world") # TypeError: 'NoneType' object is not callable
So there you go, python takes whatever we return, and stores it in place of the original function. In the normal pythonic way, it also does no checking on what the decorator retuns.
I’ll leave it there for now, hopefully this simple introduction get’s rid of some of the mystery of the decorator - maybe it gives you a starting point to think about how
@classmethod are implemented, or even how you can leverage the concept in your own projects!
Thanks for reading!