Python Data Persistence – Magic Methods
Each Python class inherits methods from its ultimate parent class – object class. Methods in objects are peculiarly named – having double underscores on either side. One such method is well known to you by now. The __init__( ) method. To display a list of methods, we have to use the built-in dir ( ) function.
Example
>>> dir(object) ['__class__' , '__delattr__' , '___dir___' , ___doc___' , '__eq__' , '__format__' , __ge__' , '__getattribute__' , '__get__' , '__hash___' , '__init__' , '___init_subclass__' , '__le__' , '___lt___' , '__ne__' , '__new___' , '__reduce__' , '__reduce_ex__' , '__repr__' , '___setattr__' , '___sizeof___' , '___str__' , '__subclasshook__']
These double underscored methods are known as ‘magic’ methods. They have a very important role in the Pythonic brand of object-oriented programming. What is so ‘magical’ about them? You’ll soon come to know.
These methods (sometimes referred to as ‘special’ or ‘dunder’ – short for double underscore) in object class are really abstract methods in the sense they don’t have any particular implementation. Its subclasses override them as per necessity. Take int class for example. It overrides___str___( ) method to return a printable string version of integer object.
Example
>>> a=123 >>> a.__str___( )
Incidentally, many of these ‘dunder’ methods are rarely called directly. A corresponding built-in function internally calls them. The str( ) function implements the __str__( ) method.
Example
>>> a=123 >>> str(a) '123'
Another example is setattr( ) function we used earlier in this chapter. It dynamically adds attribute to an object. It in fact performs operation of__setattr__( ) method. Have a look at following code:
Example
>>> class MyClass: pass >>> obj1=MyClass( ) >>> setattr(obj1,'myname','Madhav') >>> #using__setattr__( ) method >>> obj1.__setattr__(’myage',21)
Even the dir ( ) method used at the beginning of this section actually calls__dir__( ) magic method.
Example
>>> a. dir _( ) ['__repr__' , '__hash__' , '__str__' , '___getattribute___' , '__lt__' , '___le__' , '__eq__' , '___ne___' , '__gt__' , '___ge__' , '__add__' , '__radd__' ,'___sub__' , '__rsub__' , '__mul__' , ' ___rmul___', '___mod___' , '___rmod___' , '___divmod__' , '___rdivmod__' ,'__pow___' , ' ___rpow___' , ' __neg__' , '___pos___ ' , '___ abs__' ,'___bool___ ' , '__invert___' , '___shift__' , '___rlshift__ ' , '__rshift___' , '___rrshift___' , '___and___', '___rand___', '___xor__' , '__rxor__' , '__or___' , '___ror__' , '__int__' , ' __float__' , '___floordiv__' , '__rfloordiv__' ,'___truediv___' , '__rtruediv__' , '__index___' , '___new__' , '___conjugate___ ' , '__bit length__' , '___to_bytes___' , '___from bytes__' , '___t rune__' , '__floor__' , '___ceil__' , ' ___round___', '__getnewargs___' , '__format __', ' __sizeof___' , '___real___', '___imag___ ' , '__numerator__ ' , '____denominator____ ' , '____doc__' , '___setattr__' ,'___delattr____' , '__init___' , '___reduce_ex__' , '___reduce__' , '__subclasshook___ ' , '__init_subclass___ ' , '__dir__' , '__class__'] >>> #is equivalent to >>> dir(int)
It may be noted that dir (int) shows a lot more attributes than that of the object class. These are actually inherited from abstract Number class and overridden in int class. Of particular interest are methods with names indicating arithmetic operations (such as__add__, ___sub___ , ___ mul__ , and so on.) and logical operations (like ___ge__ , __ gt___ , ___eq___ , and so on.) They are mapped to respective operators so that a conventional arithmetic/logical operator invokes the respective method. In other words, a+b actually performs a . __add__(b) call.
Example
>>> a=20 >>> b=10 >>> a+b 30 >>> a.__add__(b) 30 >>> a.__mul__(b) 200 >>> a*b 200 >>> a>b True >>> a.__le__(b) False
The real magic lies ahead. You can override these methods to customize the behavior of operators to achieve operator overloading.
Example
#timerclass.py class timer: def__init__(self, hr=None, min=None): self.hrs=hr self.mins=min def__add__(self, arg): temp=timer( ) temp.hrs=self.hrs+arg.hrs temp.mins=self.mins+arg.mins if temp.mins>=60: temp.mins=temp.mins-60 temp.hrs=temp.hrs+1 return temp def__str__(self): timestring='{ } Hrs. { } mins.'.format(self.hrs,self.mins) return timestring
In the above script, the timer class has two instance attributes hrs and mins. The __add__( ) method performs addition of two timer objects. This method is invoked in response to the use of the ‘+’ operator along with two timer object operands. By the way timer class also has overridden the implementation of __str___( ) method to produce string representation of its object.
Example
>>> from timerclass import timer >>> t1=timer(2,45) >>> t2=timer(3,30) >>> t3=t1+t2 >>> print (t3) 6 Hrs. 15 mins. >>> t3=t1.__add__(t2) >>> print (t3) 6 Hrs. 15 mins.
Go ahead and overload any operator to suit the needs of your application. A list of all magic methods is provided in Appendix E.
This chapter is a very brief discussion on object-oriented programming as implemented in Python. Only those facets that are required in subsequent chapters of this book have been explained here.