All Courses
Object-Oriented Programming

Python Operator Overloading

What is Operator Overloading?

Operator overloading lets you define how built-in Python operations (+, -, *, /, len(), ==, etc.) behave on your own custom classes. This is done by implementing special dunder methods (double underscore methods, also called magic methods).

Example: 1 + 1 works because the int class implements __add__. You can do the same for your own classes.


Dunder / Magic Methods

All dunder methods follow this pattern: __method_name__

They are triggered automatically by Python operations — you don't call them directly (though you can).


Arithmetic Operations

Operation Dunder Method Example
Addition __add__ a + b
Subtraction __sub__ a - b
Multiplication __mul__ a * b
Division __truediv__ a / b
Integer division __floordiv__ a // b

Example — __add__ on a Page class

class Page:
    def __init__(self, words, page_number):
        self.words = words
        self.page_number = page_number

    def __add__(self, other):
        new_words = self.words + " " + other.words
        new_page_number = max(self.page_number, other.page_number) + 1
        return Page(new_words, new_page_number)

page1 = Page("Hello world", 1)
page2 = Page("This is page two", 2)
page3 = page1 + page2

print(page3.words)        # Hello world This is page two
print(page3.page_number)  # 3

Example — __sub__ and __mul__ on a StoreItem class

class StoreItem:
    tax = 0.13

    def __init__(self, name, price):
        self.name = name
        self.price = price
        self.after_tax_price = 0
        self.set_after_tax_price()

    def set_after_tax_price(self):
        self.after_tax_price = self.price * (1 + self.tax)

    def __sub__(self, discount):       # flat dollar discount
        return StoreItem(self.name, self.price - discount)

    def __mul__(self, value):          # percentage discount
        return StoreItem(self.name, self.price * value)

bread = StoreItem("Bread", 7)
discounted = bread - 2                 # $2 off
print(round(discounted.after_tax_price, 2))   # 5.65

sale = bread * 0.8                     # 20% off
print(round(sale.after_tax_price, 2))         # 6.33

Example — __truediv__ and __floordiv__ on a Line class

import math

class Line:
    def __init__(self, point1, point2):
        self.point1 = point1
        self.point2 = point2

    def __truediv__(self, factor):      # /
        new_p1 = (self.point1[0] / factor, self.point1[1] / factor)
        new_p2 = (self.point2[0] / factor, self.point2[1] / factor)
        return Line(new_p1, new_p2)

    def __floordiv__(self, factor):     # //
        new_p1 = (self.point1[0] // factor, self.point1[1] // factor)
        new_p2 = (self.point2[0] // factor, self.point2[1] // factor)
        return Line(new_p1, new_p2)

line1 = Line((10, 5), (20, 10))
line2 = line1 / 2
print(line2.point1)   # (5.0, 2.5)

line3 = line1 // 2
print(line3.point1)   # (5, 2)

__len__

Returns the length of an object when len() is called on it.

⚠️ Must return an integer — not a float.

class Line:
    def __len__(self):
        dist_x = (self.point1[0] - self.point2[0]) ** 2
        dist_y = (self.point1[1] - self.point2[1]) ** 2
        return round(math.sqrt(dist_x + dist_y))

print(len(line1))   # 11

Comparison Operations

Operation Dunder Method
== __eq__
!= __ne__
> __gt__
>= __ge__
< __lt__
<= __le__

Default behaviour without __eq__

Without implementing __eq__, Python checks if two objects are the exact same object in memory (like using is). Two separate instances with identical values will return False.

line1 = Line((10, 5), (20, 10))
line2 = Line((10, 5), (20, 10))   # same values, different object
print(line1 == line2)             # False — without __eq__

Implementing comparison methods

class Line:
    def __eq__(self, other):
        if not isinstance(other, Line):   # always check type first
            return False
        return self.point1 == other.point1 and self.point2 == other.point2

    def __ne__(self, other):
        return not self.__eq__(other)     # reuse __eq__ to avoid duplication

    def __gt__(self, other):
        return len(self) > len(other)

    def __ge__(self, other):
        return len(self) >= len(other)

    def __lt__(self, other):
        return len(self) < len(other)

    def __le__(self, other):
        return len(self) <= len(other)

Tip: Always check isinstance(other, ClassName) inside __eq__ before accessing attributes — otherwise comparing to a different type will crash.


String Representation Methods

__str__ — Human-readable output

Called when you use print() or str() on an object. Should return a friendly, readable string.

class Page:
    def __str__(self):
        return f"Page(text={self.text}, page_number={self.page_number})"
print(page1)   # Page(text=Hello world, page_number=1)

__repr__ — Developer / debug representation

Called when you use repr(). Should return the internal/debug representation — typically more terse, focused on identity.

class Book:
    def __repr__(self):
        return f"Book(id_number={self.id_number})"

    def __str__(self):
        output = f"Book {self.title} {self.author} {self.id_number}"
        for page in self.pages:
            output += "\n" + str(page)
        return output
print(book)        # triggers __str__ — full readable output
print(repr(book))  # triggers __repr__ — Book(id_number=1)
Method Triggered by Purpose
__str__ print(), str() Human-readable display
__repr__ repr(), debugger Internal/debug representation

Key Takeaways & Recap

Concept Summary
Dunder methods Special __method__ methods triggered by Python operators
Operator overloading Define custom behaviour for +, -, *, /, //, len(), comparisons, etc.
__add__ / __sub__ / __mul__ Arithmetic on custom objects
__truediv__ / __floordiv__ / and // division
__len__ Must return an integer
__eq__ default Checks same object in memory — override to compare values
Always check isinstance In __eq__ (and comparisons), verify type before accessing attributes
__ne__ shortcut Return not self.__eq__(other) to avoid rewriting logic
__str__ vs __repr__ __str__ = readable; __repr__ = debug/internal