In Python, function is a first order object because 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 that receives another function as argument is called 'decorator'. The behaviour of argument function is extended by the decorator without actually modifying it.
Decorator function is typically defined as follows:
def decorator(function): def wrapper(): function() return wrapper
A 'function' is passed to decorator as argument. The decorator function returns 'wrapper' function which is defined in it. The wrapper calls the passed function and extends its functionality.
Here is a normal Python function:
def decorate(): pass
You can now decorate this function to extend its behaviour by passing it to decorator
If this function is now executed, it will show output extended by decorator.
#mydecorator.py def decorator(function): def wrapper(num): print("Inside wrapper to check odd/even") if num%2 == 0: ret= "Even" else: ret= "Odd!" function(num) return ret print ("wrapper function is called") return wrapper #@my_decorator def myfunction(x): print("The number is=",x) myfunction=decorator(myfunction) no=10 print ("It is ",myfunction(no))
The myfunction() just prints out the received number. However, its behaviour is modified by passing it to a decorator. The inner wrapper receives the number and returns whether it is odd/even.
wrapper function is called Inside wrapper to check odd/even The number is= 10 It is Even
An elegant way to decorate a function is to mention just before its definition, the name of decorator prefixed by @ symbol. In this format a function() is defined as:
@decorator def decorate(): pass
Rewritten version of above example mydecorator.py is as follows:
#mydecorator.py def decorator(function): def wrapper(num): print("Inside wrapper to check odd/even") if num%2 == 0: ret= "Even" else: ret= "Odd!" function(num) return ret print ("wrapper function is called") return wrapper @decorator def myfunction(x): print("The number is=",x) no=10 print ("It is ",myfunction(no))
Let us return to definition of price property earlier in previous chapter.
We shall now use built-in property() function as decorator to define price() as a getter method in book class:
@property def price(self): return self.__price
The property object’s getter and setter methods are also decorators. We define setter method as:
@price.setter def price(self, x): self.__price=x
This eliminates need of defining getprice() and setprice() function. Rewritten book class is as below:
class Book: def __init__(self, price=40): self._price=price @property def price(self): return self._price @price.setter def price(self, price): self._price=price
>>> from book import Book >>> b1=Book() >>> b1.price 40 >>> b1.price=50 >>> b1.price 50
Refer to user.py example in previous chapter. In the User class name, email and role are called instance attributes. These attributes have different value for each object. Class attribute is one whose value is same for all instances of a class. Its value is shared by all objects. Class attribute is defined at class level outside __init__() method. Class attributes are accessed through name of class and not instance.
Let us add a class attribute 'counter' in User class. The __init__() method increments value of counter by 1 when each object is declared.
#user.py class User: counter=0 def __init__(self, name='Ram', firstname.lastname@example.org', role='Manager'): self.name=name self.email=email self.role=role User.counter=User.counter+1 print ('Number of users created so far', User.counter) def display(self): print ('name:',self.name) print ('email:',self.email) print ('role:',self.role)
Note that counter attribute is referred to by name of class (User.counter) and not object. Let us import User class and create few objects.
>>> from user import User >>> x=User() Number of users created so far 1 >>> y=User('Raja','email@example.com','guest') Number of users created so far 2
Instance methods receive object’s reference in the form of 'self'. The __init__() is an instance method. A method that is not by individual object but by the class is called class method. Python's built-in classmethod() is used as decorator to define such method in class. It has one argument 'cls' which refers to calling class. In the User class above, we shall add following class method called usercount().
@classmethod def usercount(cls): print ("no of objects",cls.counter)
This method prints value of class attribute - counter. Obviously it doesn’t access attributes of any single object. Output of above script will now be as follows:
>>> from user import User >>> x=User() Number of users created so far 1 >>> y=User('abc', 'firstname.lastname@example.org', 'root') Number of users created so far 2 >>> User.usercount() no of objects 2
The staticmethod is also a built-in function in Python standard library. A 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:
class Myclass: @staticmethod def mymethod(): pass
Even though Myclass.mymethod as well as Myclass().mymethod both are acceptable. Note that the static method doesn’t have any arguments – neither self nor cls.
Add following method in User class:
@staticmethod def countofusers(): print("no. of objects",User.counter)
This method can be called by object or class as follows:
>>> from tmp import User >>> x=User() Number of users created so far 1 >>> y=User() Number of users created so far 2 >>> z=User() Number of users created so far 3 >>> User.countofusers()
Just as function is a callable object, class instance also is a callable object. A callable object is an object which can be used and behaves like a function but might not be a function. To define class in a way that its instances should be callable objects, it should have __call__ method. The instance is called "like a function", i.e. using brackets.
Class decorator contains a custom __class__() method. It returns a custom object that extends the functionality of what the original function does, in that case a class decorator should be used.
In following example add_decorator class has __call__ method. It modifies the arguments of decorated add() function. The add() function is designed to receive variable list of numbers. The decorator doubles those numbers before calling add() function.
def __init__(self, f): self.f = f def __call__(self, *args): print ("original numbers:",args) # decorate add() before calling. doubles each number args=(2*i for i in args) self.f(*args) print ('decorator terminated') # after f actions @add_decorator def add(*args): print ("numbers received after decoration", args) s=0 for n in args: s=s+n print ('addition of numbers',s)
>>> from adddecorator import add >>> add(1,2) original numbers: (1, 2) numbers received after decoration (2, 4) addition of numbers 6 decorator terminated >>> add(10,20,30) original numbers: (10, 20, 30) numbers received after decoration (20, 40, 60) addition of numbers 120 decorator terminated