Python Forum
[Classes] Class Intermediate: Operator Overloading
Thread Rating:
  • 2 Vote(s) - 2 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Classes] Class Intermediate: Operator Overloading
#1
Before reading this tutorial, make sure you understand everything covered in the Class Basics tutorial.

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:

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:

>>> a = Vector(8, 1)
>>> a
<__main__.Vector object at 0x02403430>
Well, that's not very helpful, is it? We could get around this with:

>>> 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:

   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:

>>> 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:

>>> 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:

   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:

>>> 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:

   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:

>>> 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.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures


Messages In This Thread
Class Intermediate: Operator Overloading - by ichabod801 - Sep-15-2016, 11:01 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  [Classes] Class Intermediate: Inheritance ichabod801 2 9,345 Sep-15-2016, 11:25 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020