Operator Overloading in Python

There are several important concepts in Object Oriented Programming that very few programmers actually use. One of those concepts is Operator Overloading. This article uses the Python programming language to explain Operator Overloading.

Operator Overloading is a feature that exists in many programming languages, but you are most likely to have used it somewhere in your career if you are a C++ programmer. However, I find it more comfortable to explain the concept using the Python programming language, which is less cryptic and is simpler. Moreover, Python code looks like pseudo-code, and you could adapt the concepts into your favourite language. Before I delve into the world of Operator Overloading, let me remind you that Python is a dynamically typed language, and only simple assignments are required to set data types.

What is Operator Overloading?
Operators in any language are symbols or notations that can be used to create expressions, and provide meaning to expression statements. The simplest example is the ‘+’ operator which is used to add numbers or concatenate strings in Python. You have other built-in operators such as -, *, / and %, each used for specific operations.

In Python (and in most languages) you can only use the operators to work with data types that support these operators. For example, you cannot concatenate a string and an integer. Also, these operators cannot be used to work on instances of classes, in most cases. For example, you may want to add an instance of a class with a string or a number. In Python, if you attempt to do that explicitly, you will invoke errors.

However, you can use the concept of Operator Overloading to trick your Python interpreter and actually add fresh meanings to the built-in operators in the language. It lets objects intercept and use operations, which are inherent to Python language, but basically not supported by instances.

You achieve Operator Overloading by assigning special methods in the classes used to instantiate the objects. Let us first set up a case where we need Operator Overloading. Consider a very simple banking application. You can create a virtual bank account with a simple constructor statement as given below.

class bank:
def __init__(self, balance):
self.balance= balance
def displaybalance(self):
print self.balance

Now, we will instantiate an object from the class ‘bank’, called ‘Sam’.

>>> Sam = bank(10000)
>>> Sam.displaybalance()
10000

Suppose you want to deposit some money; then you cannot simply add a number to the instance, and expect that the bank balance be updated. See that an instance and an integer cannot be added using the ‘+’ operator.

>>> deposit = 20000
>>> Sam + deposit
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'instance' and 'int'
>>>

However, you could actually achieve the result you are looking for by using Operator Overloading, or in this specific case, by overloading the ‘+’ operator.
Python achieves Operator Overloading by using method hooks that are identified with the double underscores (‘__’) in the beginning and end of the method names as in __X__ (where X is a method name). Each operator in Python has a corresponding method name, which can intercept the operator and overload it, and execute the statements defined within these special methods. For example, __add__ is the method used to overload the ‘+’ operator. Python has several such special methods, which we will discuss later.
Essentially, the operator-overriding methods are called by an object if a corresponding operator is called. Let us rewrite the class ‘bank’ as ‘bank1’, with an additional method __add__, that overrides the ‘+’ operator. Check the code below.

>>> class bank1:
... def __init__ (self, balance):
... self.balance = balance
... def displaybalance(self):
... print self.balance
... def __add__ (self, deposit):
... self.balance = self.balance + deposit
... 

Now, I will instantiate an object called ‘Sam’.

>>> Sam = bank1(10000)

I add a deposit of 20,000 to the object ‘Sam’ using the ‘+’ operator, just the way you add two numbers or concatenate two strings.

>>> Sam + 20000

Checking for the balance attribute of the object ‘Sam’, using the displaybalance() function, I discover that the ‘+’ operator has added 20,000 to the value of the attribute ‘balance’.

>>> Sam.displaybalance()
30000

How did it all happen?
When I invoked a new instance called ‘Sam’ on the class ‘bank1’, it inherited two methods and one attribute. The methods are displaybalance() and __add__ and the attribute is ‘balance’. The method __add__ essentially accepts a parameter called ‘deposit’, and adds the variable to the ‘balance’ attribute of the object instantiated out of the class. (Remember; Python uses ‘self’ to denote instances of a class.)
The __add__ method is a special method that overrides the ‘+’ operator every time an object instantiated out of the class encounters the ‘+’ operator. Hence, the operation ‘Sam+20000’ invoked the __add__ method, which updated the ‘balance’ attribute. Note that whatever be the execution invoked by __add__ will be executed when an object encounters the ‘+’ operator.
Suppose we had changed the __add__ method in class ‘bank1’ as follows-

... def __add__ (self, deposit):
... return "Operator Overloaded! But nothing has happened to balance"

The __add__ method does nothing but return a string. Further exploring, we find that-

>>> tom = bank1(10000)
>>> tom+20000
'Operator Overloaded! But nothing has happened to balance'
>>> tom.displaybalance()
10000

I guess with this you are clear about what Operator Overloading is. Now we will try to understand why you should use Operator Overloading?

Why use Operator Overloading?
You may write thousands of lines of object-oriented code without ever using Operator Overloading. This is one of those useful techniques, which you can actually live without. But a good object oriented programmer will use this technique while designing his classes. Using Operator Overloading, object instances will act like built-in data types and you can use instances just like you use built-in data types.
Table-1 indicates some of the most common overloading methods. This is by no means an exhaustive list.

Method

Operator it Overrides

Example of Use

__add__

‘+’

X+Y, x+=y

__sub__

‘-‘

X-Y, x-=y

__repr__, __str__

Printing conversions

Print ‘X’, str(X)

__setattr__

Attribute assignment

X.var= value

__len__

Length of a container

len(list), len(string)

__iter__

Iteration contexts

For loops

__getitem__

Indexing

X[key]

__call__

Function calls

X()

Table-1: Some Common Overloading methods

The simplest idea of overloading method is __init__, which is used by most classes to initialize instances created by the class. We will explore some more operator overloading concepts in rest of the article.
In the code snippet below, we have two methods __repr__ and __del__. The method __repr__ overloads print statements, while __del__ is a pseudo destructor. The destructor is run when you need to reclaim memory space. The destructor will be executed whenever the object reference is re-assigned to some other value.

>> class address:
... def __init__ (self, name, city, zip):
... self.name = name
... self.city = city
... self.zip = zip
... def __repr__(self):
... return " I cannot tell you the address"
... def __del__(self):
... print 'Object destroyed'

Running the code, you get results as given in the code snippet below.

>>> Name = address ('Sonia', 'Delhi', 110001)
>>> Name
I cannot tell you the address
>>> Name = "Rajeev"
>>> print Name
Rajeev
>>> Name
Object destroyed
'Rajeev'
>>> Name
'Rajeev'
>>>

One of the most interesting Operator Overloading methods that Python supports is __iter__. To illustrate how it works, I am using an example from the book ‘Learning Python’ written by Mark Lutz.
This is a simple program with which you can create squares of a list of numbers. You have a class called ‘Squares’, which accepts two numbers as parameters. The method __iter__ creates an iterator. In this program, the iterator object is simply the instance itself. When you create an iterator object, you have to create a method within the class called ‘next’, which helps you use the iterator object. The iterator will not work without this method. The program essentially raises a ‘StopIteration’ when the iterator traverses through the range, and squares each number it encounters.

>>> class Squares:
... def __init__(self, start, stop):
... self.value = start -1
... self.stop = stop
... def __iter__ (self):
... return self
... def next (self):
... if self.value==self.stop:
... raise StopIteration
... self.value +=1
... return self.value**2

You will generate results as given below-

>>> SQ = Squares(1,4)
>>> for i in SQ:
... print i,
... 
1 4 9 16

I guess we have enough space to explain one of the attribute overloading methods. You can use __getattr__ method to set names to any instance that is derived out of the class. The code snippet below is self-explanatory.

>>> class class2:
... def __getattr__(self,attr):
... if attr == 'name':
... return "Sonia"
... else:
... raise AttributeError
... 
>>> A=class2()
>>> A.name
’Sonia’
>>> A.age
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 6, in __getattr__
AttributeError

There are several other Operator Overloading methods in Python. I will suggest you to refer language manuals and do some hacking to master these concepts.




Added on May 14, 2007 Comment

Comments

Post a comment