OOPLesson 3 of 3
Dunder Methods
Dunder (Double UNDERscore) methods let you hook into Python's built-in operations. Want your class to support the + operator? Define __add__. Want print() to show something useful? Define __str__. Want len() to work? Define __len__. This is operator overloading, and it is one of Python's most powerful features.
JS has a tiny version of this with Symbol.iterator, toString, and valueOf, but Python takes it much further. You can overload every arithmetic operator, every comparison, indexing, iteration, even the () call syntax. A class with __call__ becomes callable like a function.
The practical result: your custom objects can behave exactly like built-in types. Users of your class do not need to learn special method names; they use +, -, ==, for loops, and print() as they would with any built-in.
Two rules keep operator methods well behaved. First, if the other operand is a type you do not support, return NotImplemented (do not raise); Python then tries the other operand's method or raises a clean TypeError. Second, defining __eq__ sets __hash__ to None, which makes instances unhashable. If your objects must go in sets or be dict keys, define __hash__ too (or use a frozen dataclass).
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| toString() | __str__ / __repr__ | __str__ for users, __repr__ for devs |
| Cannot overload + | __add__(self, other) | Full operator overloading |
| Cannot overload == | __eq__(self, other) | Comparison operators |
| [Symbol.iterator]() | __iter__ / __next__ | Iterator protocol |
| No equivalent | __len__ | Support len() |
| Proxy get trap (clunky) | __getitem__ | Support obj[key] |
| No equivalent | __call__ | Make object callable |
Side by side
// JavaScript - limited overloading
class Vector {
constructor(x, y) { this.x = x; this.y = y; }
add(other) {
return new Vector(this.x+other.x, this.y+other.y);
}
toString() { return "("+this.x+", "+this.y+")"; }
}
// v1 + v2 <- does NOT work in JS!# Python - full operator overloading
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
def __add__(self, other): # +
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x+other.x, self.y+other.y)
def __mul__(self, s): # *
return Vector(self.x*s, self.y*s)
def __eq__(self, other): # ==
if not isinstance(other, Vector):
return NotImplemented
return self.x==other.x and self.y==other.y
def __len__(self): # len()
return 2
def __iter__(self): # for loop / unpacking
yield self.x
yield self.y
v1 = Vector(1, 2)
v3 = v1 + Vector(3, 4) # Works! -> (4, 6)
x, y = v3 # uses __iter__Exercise
Real Python, running in your browserWrite a Money class that supports these operations:
m1 = Money(100, "USD")
m2 = Money(50, "USD")
m1 + m2 -> Money(150, "USD")
m1 - m2 -> Money(50, "USD")
m1 * 3 -> Money(300, "USD")
print(m1) -> "100.00 USD"
m1 == Money(100, "USD") -> True
m1 > m2 -> True
m1 + Money(10, "EUR") -> ValueError!
(Plain numbers are fine here; real apps should use
decimal.Decimal or integer cents.)Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment