Dunder methods (__eq__/__hash__)

RoadmapsPython Backend

Overview

In Python, operators like `+`, `==`, and `<` are syntactic sugar for method calls on objects. When you run `a + b`, Python translates it to `a.__add__(b)`. Operator overloading allows you to define these double-underscore (dunder) methods in your own classes, giving your objects custom behaviour for built-in operators. For instance, defining `__eq__` lets you compare two separate instances by their data rather than their memory identity, and defining `__lt__` (less than) is required for built-in functions like `sorted()` to work. Think of operators as universal remotes: the button `+` is standard, but the specific device (the object) decides what action is taken when the button is pressed. When a dunder method doesn't know how to handle the other operand's type, it must return the singleton (an object with only one instance) `NotImplemented`, which tells Python to try the reverse operation (like `__radd__`) on the other object. Lastly, operators like `+` should return a completely new instance, leaving the original operands unchanged.

It allows custom objects to integrate seamlessly with Python's built-in syntax and functions, making code cleaner and more expressive. It also enables objects to be sorted or compared for equality without writing custom method names.

Where used: Pandas, NumPy, Pathlib

Why learn this

Code walkthrough

class Point:
  def __init__(self, x, y):
    self.x = x
    self.y = y

  def __add__(self, other):
    return Point(self.x + other.x, self.y + other.y)

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3.x, p3.y)

Focus: p3 = p1 + p2

Aha moment

class Box:
  def __init__(self, value):
    self.value = value

b1 = Box(10)
b2 = Box(10)
print(b1 == b2)

Prediction: What does this print?

Common guess: True

By default, custom classes use object identity for equality (like `is`), so two separate instances evaluate to False. You must explicitly implement `__eq__` to compare their values.

Common mistakes

Glossary

syntactic sugar
Syntax in a programming language that makes things easier to read or to express, but doesn't add any new functionality. Example: `a + b`.
operand
The object or value that an operator (like + or -) works on or manipulates. Example: `3 + 5`.

Recall questions

Understanding checks

What is the output of this script?

15, 15

The `__add__` method mutates `self` instead of creating and returning a completely new instance. Thus, both `p1` and `p3` refer to the same object, and `p1` has been permanently modified by the addition.

When defining the `__eq__` method, what is the correct way to handle comparison with an unsupported type?

Return the singleton `NotImplemented`. This tells Python to try the reverse operation on the other object before falling back to default behavior.

Returning `NotImplemented` tells Python that this method does not know how to handle the other operand's type. Python will then attempt to call the reverse operation (e.g., `__req__` or the other object's `__eq__`) before finally falling back to default behavior.

Practice tasks

Refactor User Equality

The `User` class currently uses the default equality behavior (comparing memory identity). Modify the `__eq__` method so that two `User` instances are considered equal (`==`) if they have the same `user_id`. Also ensure that comparing a `User` with a non-User object returns `NotImplemented` instead of raising an error or returning False.

Challenge

Vector Mathematics

Write a `Vector` class that initializes with `x` and `y` components. Implement `__add__` to add two vectors together returning a new `Vector`. Also implement `__eq__` so that two vectors are equal if their `x` and `y` components are identical. Ensure that if `__add__` or `__eq__` is used with a non-Vector object, it returns `NotImplemented`.

Continue learning

Previous: is vs ==

Return to Python Backend Roadmap