Abusing Python’s Operator Overloading Feature

Abusing Python's Operator Overloading Feature Featured Image

Lately, it’s been somewhat hard for me to commit to writing anything actually practical, so I hope y’all don’t mind if I continue down the rabbit hole of abusing programming language features. For instance, last time I wrote about the different ways we could implement addition without using the addition operator. Now, I’m interested in the flip side: overloading operators to have unexpected behaviors.

Table of Contents

Introduction to Operator Overloading

In Python, every time we create a class, we have the opportunity to define a variety of standard methods. For example, if you want a string representation of your object, you can override the __str__ and __repr__ methods.

Interestingly, Python also provides facilities for performing basic operations between two objects of the same type. That’s why we can add two numbers together, concatenate two strings together, and merge two lists together with the same + operator:

4 + 5  # evaluates to 9
[4] + [5]  # evaluates to [4, 5]
"4" + "5"  # evaluates to '45'

To overload the + operator for some class, we only have to implement the __add__ method. As a result, I’m interested in just how much we could abuse this feature to produce comical results. Let’s give it a try!

Overloading the Add Method for Custom Classes

To start, I had this funny idea to model products perhaps due to the proximity to Prime day. As a result, we can make a really simple class representing products that we might want to purchase:

class Product:

  def __init__(self, name: str, price: float):
    self.name = name
    self.price = price

Now, with just these two fields, we could overload the addition operator for our products. Right off the bat, however, it’s not immediately clear what this addition operator should do, which I think makes it an excellent candidate for abuse!

Adding Just One of the Fields

To start, I’m going to implement the add operator to provide the sum of the prices of the two products:

class Product:

  def __init__(self, name: str, price: float):
    self.name = name
    self.price = price

  def __add__(self, other):
    return self.price + other.price

As a result, we can add two products together as follows:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
apple + pear # evaluates to 11

This is kind of weird for a couple of reasons. First, we’re very limited in how this works since we can’t add more than two objects in an expected way:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
lemon = Product("Lemon", 3)
apple + pear + lemon

Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    apple + pear + lemon
TypeError: unsupported operand type(s) for +: 'int' and 'Product'

Which means we certainly can’t take advantage of existing functions like sum:

sum([apple, pear])
    
Traceback (most recent call last):
  File "<pyshell#21>", line 1, in <module>
    sum([apple, pear])
TypeError: unsupported operand type(s) for +: 'int' and 'Product'

Second, we probably expect the addition operator to combine the objects—perhaps into some new product via alchemy. At least, that’s how the add operator worked in the examples above.

Taken together, this means that we’ve properly abused the add operator, but it’s not like things make a lot more sense with these issues addressed. In other words, what if we tried to combine these products, so they return a new product?

Adding the Entire Object

The next approach is to find a way to combine the two objects, so information from both of the objects is merged in some meaningful way.

class Product:

  def __init__(self, name: str, price: float):
    self.name = name
    self.price = price

  def __add__(self, other):
    return Product(self.name + other.name, self.price + other.price)

In this new implementation, we lazily merge the two products by concatenating their names and summing their prices. The result is this sort of Dr. Seuss style of product alchemy:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
apple_pear = apple + pear
apple_pear.name  # evaluates to 'ApplePear'
apple_pear.price  # evaluates to 11

Though, I suppose we could have a bit more fun with it use prefixes and suffixes:

class Product:

  def __init__(self, name: str, price: float):
    self.name = name
    self.price = price

  def __add__(self, other):
    self_mid = len(self.name) // 2
    other_mid = len(other.name) // 2
    return Product(self.name[:self_mid] + other.name[-other_mid:], self.price + other.price)

The result being a slightly more interesting merging of names:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
apple_pear = apple + pear
apple_pear.name  # evaluates to 'Apar'
apple_pear.price  # evaluates to 11

And of course, the addition itself isn’t commutative, so we get these incredibly interesting variations in products based solely on the order in which they’re combined:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
pear_apple = pear + apple
pear_apple.name  # evaluates to 'Pele'
pear_apple.price  # evaluates to 11

We also grant ourselves another hilarious result which is the ability to chain addition across multiple products:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
lemon = Product("Lemon", 3)
homunculus = apple + pear + lemon
homunculus.name  # evaluates to 'Apon'
homunculus.price  # evaluates to 14

However, I don’t love that we lost the pear information from the product name. Now, it’s indistinguishable from the combination between an apple and a lemon, though I suppose that’s out of the scope for today!

For what we’ve lost, we’ve gained the wonderful ability to take advantage of the variety of iterable methods, such as sum:

homunculus = sum([pear, lemon], start=apple)
homunculus.name  # evaluates to 'Apon'
homunculus.price  # evaluates to 14

Now that’s goofy!

How to Go Completely Overboard With Operator Overloading

Up to this point, we’ve had some fun with implementing the addition operator for a class that otherwise probably shouldn’t implement it. What’s wonderful about that is that Python doesn’t just expose the addition operator, it also exposes all of these operators:

  • object.sub(self, other)
  • object.mul(self, other)
  • object.matmul(self, other)
  • object.truediv(self, other)
  • object.floordiv(self, other)
  • object.mod(self, other)
  • object.divmod(self, other)
  • object.pow(self, other[, modulo])
  • object.lshift(self, other)
  • object.rshift(self, other)
  • object.and(self, other)
  • object.xor(self, other)
  • object.or(self, other)

If you were to implement many of these as well as their in-place and augmented versions, you could probably develop your own tiny Brainfuck-like programming language within Python. Surely, if you implement enough of them, the following code would make sense:

apple = Product("Apple", 5)
pear = Product("Pear", 6)
-(apple or pear)**(apple++) 

I have no clue what it would do, but the very idea of overloading this many operators is hilarious to me. Ultimately, I think if you find a way to implement them all, you unlock sentience, so be careful.

At any rate, as much of a shitpost as this seems, there are languages like Perl that went down the operator rabbit hole unironically. In fact, I was fairly skeptical when Python first introduced the walrus operator as I’m generally against polluting a language with more obscure symbols. So, hopefully this article serves as some silly evidence for that.

With that said, I’m going to wrap it up for today. If you liked this article and want to read more like it, check out any of the following:

And as always, you can help out by heading over to my list of ways to grow the site. Otherwise, take care!

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Posts