Skip to content

Dunder Methods

Operator overloading: TypeScript can’t, Python can

Section titled “Operator overloading: TypeScript can’t, Python can”

TypeScript has no operator overloading. If you build a Vector class, you must call .add(other) explicitly — writing v1 + v2 is never valid for custom types. Python solves this through dunder methods (short for “double underscore”), also called magic methods. They are ordinary methods with names like __add__, __str__, and __len__ that Python calls automatically when you use operators or built-in functions on your objects.

The name “dunder” is Python community shorthand for names that begin and end with two underscores. You define them yourself; Python invokes them behind the scenes.

The most illustrative example is a 2D vector. In TypeScript you must name the operation explicitly. In Python you hook into + by defining __add__.

TypeScript
// TypeScript: no operator overloading — you must name the method
class Vector {
constructor(public x: number, public y: number) {}
toString(): string {
return `Vector(${this.x}, ${this.y})`;
}
add(other: Vector): Vector {
return new Vector(this.x + other.x, this.y + other.y);
}
equals(other: Vector): boolean {
return this.x === other.x && this.y === other.y;
}
get length(): number {
return 2;
}
}
const v1 = new Vector(1, 2);
const v2 = new Vector(3, 4);
const v3 = v1.add(v2); // must call .add(), can't write v1 + v2
console.log(v3.toString()); // "Vector(4, 6)"
Python
# Python: operator overloading via dunder methods
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
def __len__(self):
return 2
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # calls v1.__add__(v2) automatically
print(str(v1)) # calls v1.__str__()
print(len(v1)) # calls v1.__len__()
print(v1 == Vector(1, 2)) # calls v1.__eq__(...)

When Python evaluates v1 + v2, it internally calls type(v1).__add__(v1, v2). When you call str(v1), Python calls v1.__str__(). When you call len(v1), Python calls v1.__len__(). This means every built-in function and operator has a corresponding dunder hook you can implement.

There is also __repr__, which differs from __str__: str() is meant for end-user display while repr() (and the interactive REPL) use __repr__ for unambiguous, developer-facing output. The !r conversion flag in f-strings calls repr() on the value.

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
def __len__(self):
return 2
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(str(v1))
print(len(v1))
print(v1 == Vector(1, 2))
print(v3)
Which dunder method does Python call when you write `v1 + v2`?
What is the difference between `__str__` and `__repr__`?
Which pair of dunder methods must you implement to support the `with` statement on a custom class?
What happens to `__hash__` when you define `__eq__` on a class?