1. Description
Python is the only language with idioms . This enhances its readability and perhaps its beauty. Decorators follow the Zen of Python , aka the "Pythonic" way. Decorators are available starting in Python 2.2. PEP318 enhances them. Below is a beginner-focused tutorial on how to use decorators. If you'd like to run the code examples yourself, keep reading!
2. Decoration
A (not to be confused with the Decorator pattern) is a way to add/change the behavior of a function without changing the original function. In Python, a decorator is a design pattern that allows you to modify the functionality of a function by wrapping it in another function. External functions are called decorators, which take the original function as argument and return a modified version.decorator
Let us lead by example. Here we declare a decorator. It helps to print the output of a function instead of adding print commands which later become unwieldy and sometimes very tedious to remove.debug
def debug(function):
def wrapper(name, address):
print ('Debugging:')
func = function(name, address)
print (func)
return wrapper
@debug
def typical_crunching_function(name, city):
return 'You are '+ name + ' from '+ city
typical_crunching_function('John','Los Angeles')
Output:
Debugging:
You are John from Los Angeles
Here we define the decorator and apply it to the function using @ syntax.line 1-6
typical_crunching_function
line 8
3. Python class decorator
Class decorators were introduced in PEP3129. This was after a lot of resistance from the community, which they preferred. The main purpose was to extend the ability to decorate functions to classes.metaclasses
Below is an example of a class decorator that enhances the functionality of a function.
class Accolade:
def __init__(self, function):
self.function = function
def __call__ (self, name):
# Adding Excellency before name
name = "Excellency " + name
self.function(name)
# Saluting after the name
print("Thanks "+ name+ " for gracing the occasion")
@Accolade
def simple_function(name):
print (name)
simple_function('John McKinsey')
Output:
Excellency John McKinsey
Thanks Excellency John McKinsey for gracing the occasion
Classes are defined here that can be used to perform preprocessing and postprocessing on simple_function. In this example, we're just adding to the string, and after printing the names, thank them for honoring the occasion. This simple example demonstrates that we can easily perform pre- and post-processing on function arguments using class decorators. These preprocessing tasks can be any of the following, but are not limited to these.Cleaner
Excellency
name,
- Add timing information
- Connect to database
- close connection
- memory storage
Example 2:
class Decorator(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, instance, owner):
if instance is None:
return self
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def __delete__(self, instance):
self.fdel(instance)
def getter(self, fget):
return Decorator(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return Decorator(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return Decorator(self.fget, self.fset, fdel, self.__doc__)
class Target(object):
desc = "Amazing pyhton"
def __init__(self, attr=5):
self._x = attr
@Decorator
def show(self):
return self._x
@show.setter
def show(self, value):
self._x = value
@show.deleter
def show(self):
del self._x
4. Some built-in class decorators
Below are some built-in class decorators.
4.1 Cache/memory
Note that it is only available in Python >= 3.9. This allows previous values to be cached and reused instead of recalculating them.
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
print(factorial(10))
Output:
3628800
4.2 Properties
The meaning of property
@property turns the getter method of a class into a property. If there is a setter method, add @method.setter in front of the setter method. Using class property = property(getx, setx, delx, desc) is also possible.
The implementation is simple, so what is the principle behind it?
The pseudocode of the Property class is as follows, which involves the __get__, __set__, and __delete__ magic methods. The Decorator class is the decorator class, and Target is the target class. When you set the instance object of the decorator class as the x attribute of the target class, when you try to access the x attribute of the target class, the __get__ method of the decorator class will be triggered; when you assign a value to the x attribute of the target class, the decorator will be triggered The __setter__ method of the class; when trying to delete the x attribute of the target class, the __delete__ method of the decorator class is triggered. When accessing Target.x.__doc__, the description document of the decorator class can be printed. In fact this decorator class is also called a descriptor class. A descriptor class is an attribute that assigns an instance of a special class to a class.
Class attribute implementation:
Example 1:
This decorator allows adding setter and getter functions to properties in a class.
class Pencil:
def __init__(self, count):
self._counter=count
@property
def counter(self):
return self._counter
@counter.setter
def counter(self, count):
self._counter = count
@counter.getter
def counter(self):
return self._counter
HB = Pencil(100)
print (HB.counter)
HB.counter = 20
print (HB.counter)
Output:
100
20
Example 2:
class Decorator(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, instance, owner):
if instance is None:
return self
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def __delete__(self, instance):
self.fdel(instance)
def getter(self, fget):
return Decorator(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return Decorator(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return Decorator(self.fget, self.fset, fdel, self.__doc__)
class Target(object):
desc = "Amazing pyhton"
def __init__(self, attr=5):
self._x = attr
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = Decorator(getx,setx,delx,desc)
4.3 Cache property cached_property
This decorator allows caching of properties of a class. This is equivalent to nesting two decorators.
@cached
@property
def counter:
return self._counter
5. Conclusion
Decorators are very convenient and elegant tools, however, they are also very error-prone. Therefore, they should be used with great caution. Check out the course below to get hands-on experience with Python decorators! Education team