Python - Decorators



A Decorator in Python is a function that receives another function as argument. The argument function is the one to be decorated by decorator. The behaviour of argument function is extended by the decorator without actually modifying it.

In this chapter, we whall learn how to use Python decorator.

Function in Python is a first order object. It means that it can be passed as argument to another function just as other data types such as number, string or list etc. It is also possible to define a function inside another function. Such a function is called nested function. Moreover, a function can return other function as well.

Syntax

The typical definition of a decorator function is as under −

def decorator(arg_function): #arg_function to be decorated
   def nested_function():
      #this wraps the arg_function and extends its behaviour
      #call arg_function
      arg_function()
   return nested_function

Here a normal Python function −

def function():
   print ("hello")

You can now decorate this function to extend its behaviour by passing it to decorator −

function=decorator(function)

If this function is now executed, it will show output extended by decorator.

Example 1

Following code is a simple example of decorator −

def my_function(x):
   print("The number is=",x)

def my_decorator(some_function,num):
   def wrapper(num):
      print("Inside wrapper to check odd/even")
      if num%2 == 0:
         ret= "Even"
      else:
         ret= "Odd!"
      some_function(num)
      return ret
   print ("wrapper function is called")
   return wrapper

no=10
my_function = my_decorator(my_function, no)
print ("It is ",my_function(no))

The my_function() just prints out the received number. However, its behaviour is modified by passing it to a my_decorator. The inner function receives the number and returns whether it is odd/even. Output of above code is −

wrapper function is called
Inside wrapper to check odd/even
The number is= 10
It is Even

Example 2

An elegant way to decorate a function is to mention just before its definition, the name of decorator prepended by @ symbol. The above example is re-written using this notation −

def my_decorator(some_function):
   def wrapper(num):
      print("Inside wrapper to check odd/even")
      if num%2 == 0:
         ret= "Even"
      else:
         ret= "Odd!"
      some_function(num)
      return ret
   print ("wrapper function is called")
   return wrapper

@my_decorator
def my_function(x):
   print("The number is=",x)
no=10
print ("It is ",my_function(no))

Python's standard library defines following built-in decorators −

@classmethod Decorator

The classmethod is a built-in function. It transforms a method into a class method. A class method is different from an instance method. Instance method defined in a class is called by its object. The method received an implicit object referred to by self. A class method on the other hand implicitly receives the class itself as first argument.

Syntax

In order to declare a class method, the following notation of decorator is used −

class Myclass:
   @classmethod
   def mymethod(cls):
   #....

The @classmethod form is that of function decorator as described earlier. The mymethod receives reference to the class. It can be called by the class as well as its object. That means Myclass.mymethod as well as Myclass().mymethod both are valid calls.

Example 3

Let us understand the behaviour of class method with the help of following example −

class counter:
   count=0
   def __init__(self):
      print ("init called by ", self)
      counter.count=counter.count+1
      print ("count=",counter.count)
   @classmethod
   def showcount(cls):
      print ("called by ",cls)
      print ("count=",cls.count)

c1=counter()
c2=counter()
print ("class method called by object")
c1.showcount()
print ("class method called by class")
counter.showcount()

In the class definition count is a class attribute. The __init__() method is the constructor and is obviously an instance method as it received self as object reference. Every object declared calls this method and increments count by 1.

The @classmethod decorator transforms showcount() method into a class method which receives reference to the class as argument even if it is called by its object. It can be seen even when c1 object calls showcount, it displays reference of counter class.

It will display the following output

init called by <__main__.counter object at 0x000001D32DB4F0F0>
count= 1
init called by <__main__.counter object at 0x000001D32DAC8710>
count= 2
class method called by object
called by <class '__main__.counter'>
count= 2
class method called by class
called by <class '__main__.counter'>

@staticmethod Decorator

The staticmethod is also a built-in function in Python standard library. It transforms a method into a static method. Static method doesn't receive any reference argument whether it is called by instance of class or class itself. Following notation used to declare a static method in a class −

Syntax

class Myclass:
@staticmethod
def mymethod():
#....

Even though Myclass.mymethod as well as Myclass().mymethod both are valid calls, the static method receives reference of neither.

Example 4

The counter class is modified as under −

class counter:
   count=0
   def __init__(self):
      print ("init called by ", self)
      counter.count=counter.count+1
      print ("count=",counter.count)
   @staticmethod
   def showcount():
      print ("count=",counter.count)

c1=counter()
c2=counter()
print ("class method called by object")
c1.showcount()
print ("class method called by class")
counter.showcount()

As before, the class attribute count is increment on declaration of each object inside the __init__() method. However, since mymethod(), being a static method doesn't receive either self or cls parameter. Hence value of class attribute count is displayed with explicit reference to counter.

The output of the above code is as below −

init called by <__main__.counter object at 0x000002512EDCF0B8>
count= 1
init called by <__main__.counter object at 0x000002512ED48668>
count= 2
class method called by object
count= 2
class method called by class
count= 2

@property Decorator

Python's property() built-in function is an interface for accessing instance variables of a class. The @property decorator turns an instance method into a "getter" for a read-only attribute with the same name, and it sets the docstring for the property to "Get the current value of the instance variable."

You can use the following three decorators to define a property −

  • @property − Declares the method as a property.

  • @<property-name>.setter: − Specifies the setter method for a property that sets the value to a property.

  • @<property-name>.deleter − Specifies the delete method as a property that deletes a property.

A property object returned by property() function has getter, setter, and delete methods.

property(fget=None, fset=None, fdel=None, doc=None)

The fget argument is the getter method, fset is setter method. It optionally can have fdel as method to delete the object and doc is the documentation string.

The property() object's setter and getter may also be assigned with the following syntax also.

speed = property()
speed=speed.getter(speed, get_speed)
speed=speed.setter(speed, set_speed)

Where get_speed() and set_speeds() are the instance methods that retrieve and set the value to an instance variable speed in Car class.

The above statements can be implemented by @property decorator. Using the decorator car class is re-written as −

class car:
   def __init__(self, speed=40):
      self._speed=speed
      return
   @property
   def speed(self):
      return self._speed
   @speed.setter
   def speed(self, speed):
      if speed<0 or speed>100:
         print ("speed limit 0 to 100")
         return
      self._speed=speed
      return

c1=car()
print (c1.speed) #calls getter
c1.speed=60 #calls setter

Property decorator is very convenient and recommended method of handling instance attributes.

Advertisements