Python Decorators
Modifying Function and Class Behaviours

In this post I’ll describe decorators in Python — a powerful tool to customise function and class behaviour, and a true enrichment for every programmer and program.
Most tutorials on this start with a lengthy introduction into what functions in Python are (first class objects), and what we can do with them because of this— e.g. define inner functions and return them. However, I believe this comes quite intuitive to most people, so we will just briefly touch this (of course it is still great to know all these details!), and then get right to decorators.
Inner Functions
Consider the following example:
def func():
def inner_func():
print("Inner called")
return inner_func
f = func()
f()What does it do? Well, we define a function, which in turn defines an inner function and returns it — without calling, i.e. the function as an object. When we call func(), this object is returned, and assigned to f — meaning it now points to inner_func. Then, we can call f like any other function using parenthesis.
Introducing Decorators
With that, let’s move to decorators — which make use of the concepts briefly seen above. This is best done via an example:
def decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
def my_func():
print("Calling my_func")
f = decorator(my_func)
f()Similar to before, the decorator function defines an inner function, which now takes the function passed as input and calls it, while printing “Before” and “After” before and after its call, respectively. This is returned, so when we set f = decorator(my_func), calling f() equals calling wrapper, and thus printing “Before”, calling my_func (printing “Calling my_func”), and printing “After”.
Simplifying Decorator Definitions
We can simplify above code and “decorate” arbitrary functions using the @ symbol, such as:
def decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
@decorator
def my_func():
print("Calling my_func")
my_func()In particular, now my_func() directly is decorated with decorator, and exhibits the requested behaviour.
Decorating Functions with Arguments
If we wanted to apply above decorator for a function taking arguments, it would fail — as inside wrapper we are calling func() without. If we knew the number of arguments, we could use this — e.g. func(x, y, z). However, we would then again be tied to a specific function signature, and could not use our decorator as freely as we would like to.
Therefore, we use *args and **kwargs. I don’t want to dwell deeper on that here, but very briefly: these allow to pass an arbitrary number of arguments to a function — *args describes an arbitrary number of positional arguments, whereas **kwargs describes an arbitrary number of named arguments.
Thus, we can do the following:
def decorator(func):
def wrapper(*args, **kwargs):
print("Before")
func(*args, **kwargs)
print("After")
return wrapper
@decorator
def my_func(name):
print(f"Hello {name}!")
my_func("Jon Doe")Returning Values From a Decorated Function
Let’s have a look at how to return values from decorated functions — as of right now, if you would decorate a function with return value, such as:
@decorator
def my_func(name):
greeting_str = f"Hello {name}!"print(my_func(“Jon Doe”)) would return None. We can fix this by returning the value of func in wrapper:
def decorator(func):
def wrapper(*args, **kwargs):
print("Before")
ret_value = func(*args, **kwargs)
print("After")
return ret_value
return wrapper
@decorator
def my_func(name):
return f"Hello {name}!"
print(my_func("Returned Jon Doe"))Built-in Decorators
Actually, most of you have probably already used / seen decorators, as Python uses a few built-in decorators, such as @classmethod, @staticmethod and @property (in which we'll dive a bit deeper into here). The first two are used to define static methods not connected to a specific class instance, e.g.:
class TestClass:
@staticmethod
def static_print():
print("Static print")
TestClass.static_print()@property is used to define getters / setters.
Decorating Classes
Similar to functions, we can also decorate classes. This is less frequently used, but can come in handy when we want to monitor / control the instantiation of classes.
Let’s give an example:
@decorator
class MultiplierClass:
def __init__(self, multiplier):
print(f"Constructor")
self.multiplier = multiplier
def multiply(self, multiplicand):
return multiplicand * self.multiplier
multiplier_instance = MultiplierClass(5)
print(multiplier_instance.multiply(2))In this, we have used the previously defined decorator decorator to decorate the class MultiplierClass.
However, as we can observe when executing above program, the decorator is only applied to __init__, i.e. the class’s instantiation. But, as mentioned, this can become useful when we want to monitor / modify how classes are created — e.g. to define a singleton.
Examples
Let’s wrap this topic up by show-casing a few real-world use-cases. For more examples, I’d like to refer to my next post.
Time Execution of a Function
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
print(f"Execution took: {time.time() - start_time}s")
return wrapper
@timer_decorator
def my_func(name):
print(f"Hello {name}!")
my_func("Timed Jon Doe")Execute Function N Times
def multiplier_decorator(func):
N = 10
def wrapper(*args, **kwargs):
for _ in range(N):
func(*args, **kwargs)
return wrapper
@multiplier_decorator
def my_func(name):
print(f"Hello {name}!")
my_func("Multiplied Jon Doe")Slow Down Execution
import time
def delay_decorator(func):
sleep_duration_s = 1
def wrapper(*args, **kwargs):
time.sleep(sleep_duration_s)
func(*args, **kwargs)
return wrapper
@delay_decorator
def my_func(name):
print(f"Hello {name}!")
my_func("Sleepy Jon Doe")Conclusion
This wraps up this introduction about decorators. We learned what inner functions are, how to define function decorators and how to quickly apply them using the @ notation. We extended the simple case to a general one taking arbitrary arguments and return values, and also described how to decorate functions. Eventually, we show-cased some built-in decorators and gave pratical examples of common decorators.
Thanks for reading!
More content at PlainEnglish.io.
Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.
Interested in scaling your software startup? Check out Circuit.
