Operator overloading is a way to make the classes you create work with the standard Python operators and some of the built-in functions. As a side effect of this it makes you classes work well with other parts of Python that make use of those operators and bulit-in functions.

To illustrate some examples, I am going to use a Vector class:

- Code: Select all
`class Vector(object):`

"""

Vector

A two dimensional bound Cartesian vector.

Attributes:

x: The x coordinate of the vector terminus (int or float)

y: The y coordinate of the vector terminus (int or float)

Overridden Methods:

__init__

"""

def __init__(self, x, y = 0):

"""

Initialize a vector from a tuple or pair of numbers.

Parameters:

x: The x coordinate or a sequence of x and y coordinates (sequence or number)

y: The y coordinate (number)

"""

# check for sequence initialization

if isinstance(x, (tuple, list)):

self.x = x[0]

self.y = x[1]

# otherwise use number initialization

else:

self.x = x

self.y = y

You may think there's a lot of commenting going on here. It's useful, though. Run the above code and type "help(Vector)". It will spew out all of the block comments for the class. This is useful when you are trying to debug, especially when trying to debug code you wrote three years ago that your boss asked you to add a new feature to. As a way of introducing our first overlaoded operator, Imagine you are trying to debug a problem with a particular vector:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> a

<__main__.Vector object at 0x02403430>

Well, that's not very helpful, is it? We could get around this with:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> print(a.x, a.y)

8 1

That's a pain in the butt, and usually when you're debugging you have a pain in the butt already. The key to getting around this is knowing that typing 'a' into the interpreter is the same as typing 'repr(a)' into the interpreter, and typing 'print(a)' into the interpreter is the same as typing 'str(a)' into the interpreter. We can use operator overloading to make Python do what we want in those situations:

- Code: Select all
`def __repr__(self):`

"""

Computer readable representation.

"""

return 'Vector({}, {})'.format(self.x, self.y)

def __str__(self):

"""

Human readable representation.

"""

return '({}, {})'.format(self.x, self.y)

Add the above code to your Vector class definition. Now we get:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> a

Vector(8, 1)

>>> print(a)

(8, 1)

Much better. But you may be wondering why I made the easier to type version (a) return a more complicated result. And why did I call it "computer readable?" The convention for repr is that eval(repr(x)) == x. So the return value of __repr__ should be something that evaluates into an equivalent object. For complicated objects that change a lot after they are created, this can be a pain. In that case the convention is to put the object type and some other useful information inside angle brackets. Which is exactly what we saw before we implemented __repr__ for the Vector class.

So let's try out our new repr:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> eval(repr(a)) == a

False

So why didn't that work the way I said it would? Because we haven't told Python how to judge the equality of Vector objects. == is an operator after all, and it's one we haven't overloaded yet. Python, not having been told how to judge equality of Vectors, judges it by the memory address of the objects. eval(repr(a)) resides at a different memory location than a itself does. So let's tell Python how to judge equality:

- Code: Select all
`def __eq__(self, other):`

"""

Equality testing.

Parameters:

other: What to check equality against. (Vector)

"""

return self.x == other.x and self.y == other.y

Now it works:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> eval(repr(a)) == a

True

>>> b = Vector(8, 1)

>>> a == b

True

>>> c = Vector(8, 2)

>>> a == c

False

>>> a == (8, 1)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "vector.py", line 39, in __eq__

return self.x == other.x and self.y == other.y

AttributeError: 'tuple' object has no attribute 'x'

That last AttributeError is acutally bad form. The convention in Python is to return the special object NotImplemented if the equality comparison is not supported. On the other hand, we might want to support equality with tuples. So let's rewrite our __eq__ method:

- Code: Select all
`def __eq__(self, other):`

"""

Equality testing.

Parameters:

other: What to check equality against. (Vector)

"""

# vector to vector

if isinstance(other, Vector):

return self.x == other.x and self.y == other.y

# vector to sequence

elif isinstance(other, (tuple, list)):

return self.x == other[0] and self.y == other[1]

# vector to anything else

else:

return NotImplemented

And when we try it out:

- Code: Select all
`>>> a = Vector(8, 1)`

>>> b = Vector(8, 1)

>>> a == b

True

>>> a == (8, 1)

True

>>> b == [8, 1]

True

>>> a == 5

False

>>> a == (8, 1, -1)

True

Now when we try to equal something that doesn't make sense, Python gracefully gives a False instead of an error. And note that we may have wanted that last comparison to be False. I'm trying to be simple here, but it is good to be careful what you code lest you get unexpected results.