Python Decorators 101

A lot has been written over time about these cheeky little creatures and I couldn't possibly shy away from throwing a bit of code together.
Before I start, what is a decorator? It's a little monkey that adds extra functionality to your function (or class).
Let's hit it. Create a file called decorator.py
and copy and paste (or type) the below example:
import typing
def metadata(func: typing.Callable) -> typing.Callable:
def call(*args, **kwargs):
metadata_ = {
"function_name": func.__name__,
"docstring": func.__doc__
}
print(metadata_)
return func(*args, **kwargs)
return call
In this case it's hopefully obvious that this decorator (1) takes a function (callable) as an input (2) prints some metadata about the function passed and (3) outputs the very same function. Let's go ahead and decorate.
@metadata
def get_version() -> str:
""" Returns your python interpreter version """
return sys.version
So this is how you decorate! That looks familiar right? Like @staticmethod
or @property
in a class or @login_required
in django. Let's get into out REPL and execute get_version()
python -i decorator.py
>>> get_version()
{'function_name': 'get_version', 'docstring': 'Returns your python interpreter version'}
'3.9.10 (main, Jan 29 2022, 05:27:31) \n[GCC 10.2.1 20210110]'
We can see the metadata at runtime but if we do the below:
>>> get_version.__name__
'call'
>>> get_version.__doc__
>>>
What happened? __name__
should be get_version
and the docstring should definitely not be empty.
functools
to the rescue
Let's import wraps
from functools
and add it to our metadata
decorator like this:
import typing
from functools import wraps
def metadata(func: typing.Callable) -> typing.Callable:
@wraps(wrapped=func)
def call(*args, **kwargs):
metadata_ = {
"function_name": func.__name__,
"docstring": func.__doc__
}
print(metadata_)
return func(*args, **kwargs)
return call
Repeating the previous exercise we now see the metadata remains intact.
# python -i decorator.py
>>> get_version.__name__
'get_version'
>>> get_version.__doc__
' Returns your python interpreter version '
>>>
Here's a slightly more complex decorator example if you feel adventurous
Have a read about functools.wraps too if you wish to know more.
Enjoy coding!