What are ‘decorators’ in Python programming?

Reza Kalantar
3 min readJan 5, 2023

--

Image by Andrew Knight

One advanced technique in Python programming is called the “decorator”. Decorators are functions that modify the behavior of other functions. They are useful for adding additional functionality to existing functions, such as timing, or caching, without modifying the original function code.

Let’s look at an example of how to use a decorator to add timing to a function. First, we write a sample function to add one second delay to code:

import time

def long_running_function():
time.sleep(1)

long_running_function() # Output is nothing but the operation finishes after 1 second

Now, let’s add a decorator to time this or any function:

def time_function(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {end - start:.6f} seconds")
return result
return wrapper

@time_function
def long_running_function():
time.sleep(1)

long_running_function()

The output of running the same function with the time_function decorator is:

long_running_function took 1.004384 seconds

Interesting!

Let’s look at another example that performs argument validation of our functions. We start with a simple function to add two numbers:

def add(x, y):
return x + y

print(add(1,"a"))

We expect to get an error here as we cannot add 1 and “a” which have different data types. Here is the error:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

We can decorate this function with a validator function that inspects the arguments and performs the operation of our choice:

def validate_arguments(func):
def wrapper(*args, **kwargs):
for arg in args:
if not isinstance(arg, int):
raise ValueError("Here is our custom error function: All arguments must be integers")
return func(*args, **kwargs)
return wrapper

@validate_arguments
def add(x, y):
return x + y

print(add(1, "a"))

Now, the output of the function is:

ValueError: Here is our custom error function: All arguments must be integers

Another interesting example can be an authentication decorator:

 def require_auth(func):
def wrapper(*args, **kwargs):
if not authenticated():
raise Exception("Not authenticated")
return func(*args, **kwargs)
return wrapper

@require_auth
def protected_function():
print("Protected function")

def authenticated():
return False

try:
protected_function()
except Exception as e:
print(e)

def authenticated():
return True

protected_function()

The output is:

Not authenticated
Protected function

Interesting! now we can see some potential real-life use cases. An authentication decorator can be used to ensure that only authenticated users are allowed to access certain functions. This can be useful for adding security to your code and protecting sensitive data.

In these examples, the decorators are defined to take a function as an argument and return a wrapper function that adds the desired additional functionality. The decorator syntax “@decorator_name” is used to apply the decorator to the function.

Decorators can be a very useful technique in Python programming, and they can be combined to add multiple layers of functionality to a function. However, they can also make the code more difficult to understand and debug, so it is important to use them judiciously.

If you find this tutorial helpful or would like to reach out, feel free to get in touch with me on here, Github or Linkedin. Happy coding!

--

--

Reza Kalantar

Medical AI Researcher by Profession • Scientist/Engineer by Trade • Investor by Instinct • Explorer by Nature • Procrastinator by Choice