Writing a decorator in Python
Decorators are fairly uncommon in JS or TS although a few years ago they were kind of all the rage with the release of ES6 classes. I certainly remember Redux and MobX documenting a use of them for a time while NestJS makes use of them today. I think when it comes to React, their use fell off due to the transition away from class components to function components and the introduction of hooks. A limitation of decorator use in JS/TS is that they only work with classes. They can be applied to properties and methods but only as part of a class. Another thing to note is that they are still considered experimental, another turn off.
This isn’t the case in Python. You can of course use them with classes but you
can also wrap a normal function and it might even be more “pythonic” to use a
decorator instead of calling a function multiple times. I remember when I first
saw something like
myFunc(a)(b) in some now ancient jQuery documentation and
was totally baffled. I had had no idea that was even a possibility.
Let’s write a simple decorator in Python and use it:
def provide_message(message: str) -> Callable: def decorator(func: Callable) -> Any: return func(message) return decorator @provide_message("Hello") def print_message(message: str) -> None: print(message)
When this code is run you’ll see
Hello printed in the terminal. Something to
be note is that we didn’t call
print_message and yet we saw the
printed to the terminal and this is because the
decorator function called it
and passed along the message argument. Just a little thing to be aware of. The
equivalent of this code without the use of a decorator would be the following:
print_greeting = provide_message("Hello")(print_message)
We have two problems with the above Python decorator, we probably don’t want our
print_message to be called when we apply a decorator to it.
The other is that we can’t provide any more arguments to it. How do we solve
this? With more functions of course! Lets do a little refactor:
def provide_message(message: str) -> Callable: def decorator(func: Callable) -> Callable: def capture(*args, **kwargs) -> Any: return func(message, *args, **kwargs) return capture return decorator @provide_message("Hello") def print_message(message: str, punctuation = "") -> None: print(message + punctuation) print_message() print_message("!") print_message(punctuation="?")
Calling our little module we see:
Hello Hello! Hello?
Hooray! So we solved two problems using the
capture function. One, we don’t
immediately call our function when it is defined and only when it is
specifically called and two we can pass arguments and keyword arguments.