Decorators in Python
Published: Sat Aug 26 2023
Why the name Decorator? What’s the need to decorate Python code? Let’s find out in this byte.
Note: The decorator design pattern and the decorators in Python are not the same!
In Python, functions are treated as objects. What are its advantages?
- A function can be passed as an argument to another function.
- Functions can be assigned to variables.
- Also, a function can be defined inside another function.
Alright, let me present you with a code snippet. Take a look.
def memoize(func):
cache = {}
def wrapper(n):
if n not in cache:
cache[n] = func(n)
else:
print('from cache')
return cache[n]
return wrapper
def calculate_factorial(n):
if n == 0:
return 1
else:
return n * calculate_factorial(n - 1)
fact = memoize(calculate_factorial)
print(fact(5))
print(fact(5)) If you run the above code, the console will look like this:
120
from cache
120 Let me break it down:
- I am passing the
calculate_factorialfunction as an argument to a function known asmemoize. - When
memoizeis called, it will return a function namedwrapper. - I am storing the
wrapperfunction in a variablefact. - Whenever I call
fact,wrappergets called.
The takeaway from the above example is that, for caching purposes, I am wrapping a function and calling a function inside it. This is the concept of a decorator. So a decorator is nothing but the wrapping of functions. The same example can be converted into a decorator.
def memoize(func):
cache = {}
def wrapper(n):
if n not in cache:
cache[n] = func(n)
else:
print('from cache')
return cache[n]
return wrapper
@memoize
def calculate_factorial(n):
if n == 0:
return 1
else:
return n * calculate_factorial(n - 1)
print(calculate_factorial(5))
print(calculate_factorial(5)) Here’s the key difference between the code snippets:
# Before
fact = memoize(calculate_factorial)
print(fact(5))
# After
@memoize
def calculate_factorial(n):
...
print(calculate_factorial(5)) - In the first snippet, I explicitly create a memoized version of the function by calling
memoize(calculate_factorial). - In the second snippet, I apply the
@memoizedecorator directly to thecalculate_factorialfunction.
Class Decorators
The concept we saw so far will apply to class decorators too, but the catch is extending the behaviour of classes instead of functions. I have a class named User that will have a constructor to initialize the variable name,
class User:
def __init__(self, name):
self.name = name Say I need to log a message whenever a new user is created. The simplest solution is to add a print statement inside the constructor so that whenever an object is created, the constructor is called, which will initialize a variable name and print the message:
class User:
def __init__(self, name):
self.name = name
print(f"[User] {self.name} is created!") But we are going to get the same result in the decorator way:
def logger(cls):
def log(self, message):
print(f"[{cls.__name__}] {message}")
cls.log = log
return cls
A function named logger will dynamically add a method named log to the class that is passed to the logger function (in this case, User). Here’s the complete code:
def logger(cls):
def log(self, message):
print(f"[{cls.__name__}] {message}")
cls.log = log
return cls
@logger
class User:
def __init__(self, name):
self.name = name
self.log(f"{self.name} is created!")
customer = User("Roopesh") In the above code snippet, the logger function (decorator) is applied to the class User. When the User class is defined, the decorator is called with cls referring to the User class.