A Dive Into Python Decorators
Understand logged, time, nested, and other built-in Python decorators
Introduction
A Python decorator is a function that modifies another function and returns a function. The Python decorator concept is a little hard to understand. Let us go through in detail to understand Python decorators.
Contents
1. What is a Python Decorator?
2. Create a Simple Python Decorator
3. Debugging-Fix the Function Name and Docstring
4. Nested Decorators
5. Passing Arguments to the Decorator
6. Passing Arguments to the Function
7. Logged Decorator
8. Time Decorator
9. Functools.lru_cache(Memoize)
10. Uses of Decorators
11. Built-in Python Decorators
12. Conclusion
Python Decorator
- Decorators were introduced in Python 2.4.
- The Python decorator function is a function that modifies another function and returns a function.
- It takes a function as its argument.
- It returns a closure. A closure in Python is simply a function that is returned by another function.
- There is a wrapper function inside the decorator function.
- It adds some additional functionality to the existing function without changing the code of the existing function. This can be achieved by decorators.
- A decorator allows you to execute code before and after the function; they decorate without modifying the function itself.
- In Python, a decorator starts with the
@
symbol followed by the name of the decorator function. - Decorators slow down the function call.

Create a Simple Python Decorator
Let's code and see how it works.
- I have a simple function below which gives the product of three numbers. Just call it the original function.
2. Now I want to modify the functionality of the original function without modifying the code in the original function. This can be done by using the Python decorator.
Now I have the following function:
- I have the decorator function called
decor_func
. Inside the decorator function, I have another function calledwrapper_func
which accepts any number of arguments —*args
and**kwargs
. - The
decor_func
will return thewrapper_func
. - The
orig_func
is decorated by thedecor_func
.
Let's see how it works.
orig_func (7,9,11)
The output is as follows:
This is the product: 693
Now, I want to add some functionality without changing the above function. So I am using a decorator function that contains a wrapper function that has some code and is executed with the original function.
- Functions are objects.
- Functions can be defined in a function.
- Functions can be assigned to a variable.
You can call the decorator function by
orig_func=decor_func(orig_func)
or
@decor_func

The orig_func=decor_func(orig_func)
or the @decor_func
runs the wrapper_func
in the decor_func
.
When I execute the above code the output is as follows:
Execute this code before the main function is executed.
This is the product: 693
Now you can understand how Python decorators work.

Now you know how a basic decorator works. We can explore some additional concepts of decorators.
Debugging-Fix the Function Name and Docstring
If you do the help
on the original function, it looks like this:
It gives the following:
Help on function wrapper_func in module __main__:
wrapper_func(*args, **kwargs)
This is the wrapper function
This shows the docstring of the wrapper function. It should show the original function name and the docstring.
We can achieve this with the following workaround:
dir(orig_func)
The following attributes are there in the Python function:
[‘__annotations__’, ‘__call__’, ‘__class__’, ‘__closure__’, ‘__code__’, ‘__defaults__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__get__’, ‘__getattribute__’, ‘__globals__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__kwdefaults__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__name__’, ‘__ne__’, ‘__new__’, ‘__qualname__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’]
You can assign the function to a variable and display the name and docstring.
orig_func
returns the product of a,b,c
or
We can use Wraps from the functools
module. It is a function decorator which applies update_wrapper()
to the decorated function. Applying functools.wraps
to the wrapper returned by the decorator carries over the docstring and other metadata of the input function which is orig_func
.The functools
module was introduced in Python 2.5. It includes the function, which copies the name, module, and docstring of the decorated function to its wrapper. We can change the code as follows:
Nested Decorator
We can also have more than one decorator like the following:
This is the first decorator
This is the second decorator
Nested Decorators

Passing Arguments to the Decorator
You can also pass parameters to the decorator to be used in the code.
For example,
orig_func(7,9,11)
The output of the above is as follows:
Flag is False
This is the product: 693
Let's go through how this works:
- The
decor_factory
function is not a decorator function. Instead, it returns a decorator when called. - Any arguments passed to
decor_factory
can be referenced (as free variables) inside the function decor. - The
decor_factory
function is a decorator factory function. It is a function that creates a new decorator each time it is called. - In this case, the
decor_factory
returns the decor function. Thedeco
function is the real decorator which takes thefunction(fn)
as its argument.

Passing Arguments to the Function
We can pass the arguments to the decorator wrapper function, as shown:
Arguments are passed: San Jose , California
My city is San Jose
My State is California
If you’re making a general-purpose decorator — one you’ll apply to any function or method — then just use *args, **kwargs
@Logged Decorator
Try to create a log like when the function is called using the function name, etc.
The output is as shown:
The product of a*b*c: 90
product: called 2022–01–16 04:41:50.483630+00:00
@Time Decorator
Create a decorator to calculate how much time the function took to execute.
The output will be the following:
The product of a*b*c: 90
product ran for 0.001004s
You can also call the log and time together.
The output will be the following:
The product of a*b*c: 90
product ran for 0.000092s
product: called 2022–01–16 04:59:14.443023+00:00
@functools.lru_cache(Memoize)
@lru_cache
decorator, which gives you the ability to cache the result of your functions using the Least Recently Used (LRU) strategy. It uses the catching technique.lru
stands for “least recently used.” For more information, please check the following link.- The performance can be increased using the
@lru_cache decorator.
- Catching can be implemented by using a dictionary.
- Memoization is a form of cache. We cache the previously calculated Factorial numbers so that we don’t have to calculate them again.
Here are some examples:
Without Cache
Here we created a factorial function:
The output is as shown:
Factor5!
Factor4!
Factor3!
Factor2!
Factor1!
120
If I try factor 7
, then the output is
factor(7)
And the detailed output is the following:
Factor7!
Factor6!
Factor5!
Factor4!
Factor3!
Factor2!
Factor1!
5040
With Cache
Now we are using the @lru_cache.Functools.cache
is available from Python 3.9. Before 3.9, use @lru_cache
.
@lru_cache
has a maxsize
parameter, which has a default value of 128 — which means the cache will hold at most 128 entries at any time. The acronym LRU stands for “Least Recently Used,” meaning that older entries that have not been read for a while are discarded to make room for new ones.
The output for factor(5)
is the following:
Factor5!
Factor4!
Factor3!
Factor2!
Factor1!
120
If you run the function for factor 6
— factor(6)
, then the output will be
Factor6!
720
As you can see, with cache it uses the factor 1 to factor 5 from the cache and only calculates the cache 6.
If you try the factor for 4, then the output is
24
In this case, all are selected from the cache.
So @lru_cache
improves the performance. You can also use the @time
decorator to capture the time-end-start
.
Uses of Decorators
- Access control and validation.
- Logging.
- Timing.
- Caching.
- Don’t want to change the source function.
- Trying to reuse a function that might fail.

Built-in Python Decorators
Here are a few important Python built-in decorators:
@functools.wraps
This is a convenience function for invokingupdate_wrapper()
as a function decorator when defining a wrapper function.@functools.lru_cache
Decorator to wrap a function with a memoizing callable that saves up to themaxsize
most recent calls. It can save time when an expensive or I/O bound function, and it is periodically called with the same arguments.@atexit.register
Register func as a function to be executed at termination.@classmethod
Return a class method for function.@property
Return a property attribute.
Conclusion
As you see, Python decorators are more powerful and can be used in a lot of scenarios. Maybe you can create your own decorator and use the above concepts.
References
- https://gist.github.com/Zearin/2f40b7b9cfc51132851a
- https://github.com/chiphuyen/python-is-cool
- https://github.com/lord63/awesome-python-decorator
- https://www.python.org/dev/peps/pep-0318/
- https://www.python.org/dev/peps/pep-3129/
- https://wiki.python.org/moin/PythonDecorators
- https://wiki.python.org/moin/PythonDecoratorLibrary
Want to Connect?Please feel free to connect with me on LinkedIn