Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced Python Operator Overloading with Custom Point Class

Tech May 12 2

Operator Overloading in Python

Operator overloading is a fundamental concept in object-oriented programming that allows developers to define custom behaviors for standard operators when applied to user-defined types. In Python, this powerful feature enables us to create more intuitive and natural interactions with our custom classses. Let's explore how to implement operator overloading using a custom Point class as our example.

The Coordinate Class

First, let's define a simple Coordinate class that represents a point in 2D space with x and y coordinates.

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __str__(self):
        return f'({self.x}, {self.y})'

Testing the basic functionality:

c1 = Coordinate()
print(c1)  # Output: (0, 0)
print(repr(c1))  # Output: Coordinate(0, 0)

c2 = Coordinate(3, 7)
print(c2)  # Output: (3, 7)

Implementing Special Methods

Python provides special methods (also known as magic methods) that allow us to customize the behavior of our objects. Let's implement some of these methods to our Coordinate class.

Index Access Methods

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __getitem__(self, index):
        if index in range(-2, 2):
            return self.y if index in (1, -1) else self.x
        raise IndexError("Index out of range")
    
    def __setitem__(self, index, value):
        if index in (0, -2):
            self.x = value
        elif index in (1, -1):
            self.y = value
        else:
            raise IndexError("Index out of range.")

Testing index access:

c = Coordinate(4, 9)
print(c[0], c[1])  # Output: 4 9
print(c[-2], c[-1])  # Output: 4 9

c[0] = 10
print(c)  # Output: (10, 9)
c[1] = 20
print(c)  # Output: (10, 20)

Length and Boolean Methods

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __len__(self):
        return 2
    
    def __bool__(self):
        return self.x >= 0 and self.y >= 0

Testing length and boolean:

c = Coordinate(3, 4)
print(len(c))  # Output: 2
print(bool(c))  # Output: True

c = Coordinate(-1, 5)
print(bool(c))  # Output: False

Unary Operators

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __neg__(self):
        return Coordinate(-self.x, -self.y)
    
    def __pos__(self):
        return Coordinate(self.x + 1, self.y), Coordinate(self.x, self.y + 1)
    
    def __abs__(self):
        return Coordinate(abs(self.x), abs(self.y))

Testing unary operators:

c = Coordinate(-3, 5)
print(-c)  # Output: Coordinate(3, -5)
print(+c)  # Output: (Coordinate(4, 5), Coordinate(-3, 6))
print(abs(c))  # Output: Coordinate(3, 5)

Callable Method

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __call__(self, dx=0, dy=0):
        return Coordinate(self.x + dx, self.y + dy)

Testing callable:

c = Coordinate(2, 3)
new_c = c(4, 5)
print(new_c)  # Output: Coordinate(6, 8)

Comparison Operators

Let's implement comparison operators to compare Coordinate objects.

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __eq__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and self.y == other.y
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __lt__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and self.y < other.y
    
    def __gt__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x < other.x and self.y == other.y
    
    def __le__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and abs(self.y - other.y) == 1
    
    def __ge__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.y == other.y and abs(self.x - other.x) == 1

Testing comparison operators:

c1 = Coordinate(2, 5)
c2 = Coordinate(2, 5)
c3 = Coordinate(3, 5)

print(c1 == c2)  # Output: True
print(c1 != c3)  # Output: True
print(c1 < c2)   # Output: False
print(c1 > c3)   # Output: False
print(c1 <= c2)  # Output: True
print(c1 >= c3)  # Output: False

Bitwise Operators

We can also overload bitwise operators for our Coordinate class.

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __and__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x != other.x and self.y != other.y
    
    def __or__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x or self.y == other.y
    
    def __xor__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return (self.x == other.x and self.y != other.y) or (self.x != other.x and self.y == other.y)
    
    def __invert__(self):
        return Coordinate(self.y, self.x)
    
    def __lshift__(self, other):
        if not isinstance(other, int):
            return NotImplemented
        return Coordinate(self.x + other, self.y)
    
    def __rshift__(self, other):
        if not isinstance(other, int):
            return NotImplemented
        return Coordinate(self.x, self.y + other)

Testing bitwise operators:

c1 = Coordinate(1, 2)
c2 = Coordinate(3, 4)

print(c1 & c2)  # Output: True
print(c1 | c2)  # Output: False
print(c1 ^ c2)  # Output: True
print(~c1)      # Output: Coordinate(2, 1)
print(c1 << 2)  # Output: Coordinate(3, 2)
print(c1 >> 3)  # Output: Coordinate(1, 5)

Arithmetic Operators

Finally, let's implement arithmetic operators for our Coordinate class.

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __add__(self, other):
        if hasattr(other, '__getitem__') and len(other) == 2:
            return Coordinate(self.x + other[0], self.y + other[1])
        return NotImplemented
    
    def __sub__(self, other):
        if hasattr(other, '__getitem__') and len(other) == 2:
            dx, dy = tuple(map(float, other))
            return ((self.x - dx)**2 + (self.y - dy)**2)**0.5
        return NotImplemented
    
    def __mul__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return (self.x == other.x and abs(self.y - other.y) == 1) or (self.y == other.y and abs(self.x - other.x) == 1)
    
    def __truediv__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        if self.x == other.x and self.y != other.y:
            return 1 if self.y < other.y else -1
        if self.y == other.y and self.x != other.x:
            return 1 if self.x < other.x else -1
        return False
    
    def __pow__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        if self.x == other.x and self.y != other.y:
            step = 1 if self.y < other.y else -1
            return [Coordinate(x, self.y) for x in range(self.x + step, other.x, step)]
        if self.y == other.y and self.x != other.x:
            step = 1 if self.x < other.x else -1
            return [Coordinate(self.x, y) for y in range(self.y + step, other.y, step)]
        return None

Testing arithmetic operators:

c1 = Coordinate(1, 2)
c2 = Coordinate(4, 2)
c3 = Coordinate(4, 5)

print(c1 + c2)  # Output: Coordinate(5, 4)
print(c1 - c2)  # Output: 3.0
print(c1 * c2)  # Output: True
print(c1 / c2)  # Output: 1
print(c1 ** c2)  # Output: [Coordinate(2, 2), Coordinate(3, 2)]

Complete Implementation

Here's the complete Coordinate class with all the implemented special methods:

class Coordinate:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y
    
    def __repr__(self):
        return f'Coordinate({self.x}, {self.y})'
    
    def __str__(self):
        return f'({self.x}, {self.y})'
    
    def __getitem__(self, index):
        if index in range(-2, 2):
            return self.y if index in (1, -1) else self.x
        raise IndexError("Index out of range")
    
    def __setitem__(self, index, value):
        if index in (0, -2):
            self.x = value
        elif index in (1, -1):
            self.y = value
        else:
            raise IndexError("Index out of range.")
    
    def __len__(self):
        return 2
    
    def __abs__(self):
        return Coordinate(abs(self.x), abs(self.y))
    
    def __bool__(self):
        return self.x >= 0 and self.y >= 0
    
    def __neg__(self):
        return Coordinate(-self.x, -self.y)
    
    def __pos__(self):
        return Coordinate(self.x + 1, self.y), Coordinate(self.x, self.y + 1)
    
    def __call__(self, dx=0, dy=0):
        return Coordinate(self.x + dx, self.y + dy)
    
    def __eq__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and self.y == other.y
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __lt__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and self.y < other.y
    
    def __gt__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x < other.x and self.y == other.y
    
    def __le__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x and abs(self.y - other.y) == 1
    
    def __ge__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.y == other.y and abs(self.x - other.x) == 1
    
    def __and__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x != other.x and self.y != other.y
    
    def __or__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return self.x == other.x or self.y == other.y
    
    def __xor__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return (self.x == other.x and self.y != other.y) or (self.x != other.x and self.y == other.y)
    
    def __invert__(self):
        return Coordinate(self.y, self.x)
    
    def __lshift__(self, other):
        if not isinstance(other, int):
            return NotImplemented
        return Coordinate(self.x + other, self.y)
    
    def __rshift__(self, other):
        if not isinstance(other, int):
            return NotImplemented
        return Coordinate(self.x, self.y + other)
    
    def __add__(self, other):
        if hasattr(other, '__getitem__') and len(other) == 2:
            return Coordinate(self.x + other[0], self.y + other[1])
        return NotImplemented
    
    def __sub__(self, other):
        if hasattr(other, '__getitem__') and len(other) == 2:
            dx, dy = tuple(map(float, other))
            return ((self.x - dx)**2 + (self.y - dy)**2)**0.5
        return NotImplemented
    
    def __mul__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        return (self.x == other.x and abs(self.y - other.y) == 1) or (self.y == other.y and abs(self.x - other.x) == 1)
    
    def __truediv__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        if self.x == other.x and self.y != other.y:
            return 1 if self.y < other.y else -1
        if self.y == other.y and self.x != other.x:
            return 1 if self.x < other.x else -1
        return False
    
    def __pow__(self, other):
        if not isinstance(other, Coordinate):
            return NotImplemented
        if self.x == other.x and self.y != other.y:
            step = 1 if self.y < other.y else -1
            return [Coordinate(x, self.y) for x in range(self.x + step, other.x, step)]
        if self.y == other.y and self.x != other.x:
            step = 1 if self.x < other.x else -1
            return [Coordinate(self.x, y) for y in range(self.y + step, other.y, step)]
        return None

This implementation demonstrates the power and flexibility of Python's operator overloading capabilities, allowing us to create intuitive and natural interactions with our custom Coordinate class.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.