🪄 Python Magic Methods: Teaching Your Objects to Speak, Compare & Calculate
Imagine you have a toy robot. Out of the box, it just sits there. But what if you could teach it to introduce itself, compare itself to other robots, and even add up with another robot to make a super-robot? That’s exactly what Magic Methods do for Python objects!
🎭 The Analogy: Objects as Actors
Think of every Python object as an actor on a stage. Magic methods are the scripts that tell the actor how to:
- Introduce themselves (
__str__,__repr__) - Decide who’s taller, older, or better (
__eq__,__lt__, etc.) - Do math together (
__add__,__sub__, etc.) - Act like a box holding things (
__len__,__getitem__, etc.) - Be called like a function (
__call__) - Be trustworthy and give yes/no answers (
__hash__,__bool__)
📖 Chapter 1: __str__ and __repr__ — The Introduction Cards
What’s the Story?
When you meet someone new, they tell you their name. When you print an object, Python asks: “Hey object, how should I introduce you?”
__str__= The friendly introduction for humans__repr__= The official introduction for developers
Simple Example
class Pet:
def __init__(self, name, species):
self.name = name
self.species = species
def __str__(self):
return f"Hi! I'm {self.name} the {self.species}!"
def __repr__(self):
return f"Pet('{self.name}', '{self.species}')"
buddy = Pet("Buddy", "Dog")
print(buddy) # Hi! I'm Buddy the Dog!
print(repr(buddy)) # Pet('Buddy', 'Dog')
When to Use Which?
| Method | Purpose | Audience |
|---|---|---|
__str__ |
Pretty, readable | End users |
__repr__ |
Precise, recreatable | Developers |
💡 Pro Tip: If you only define one, define
__repr__. Python will use it as a fallback for__str__too!
⚖️ Chapter 2: Comparison Magic Methods — The Judges
What’s the Story?
Imagine two kids arguing about who has more candies. You need a judge to compare them. Comparison magic methods are those judges!
The Six Comparison Operators
graph TD A["Comparison Methods"] --> B["__eq__ =="] A --> C["__ne__ !="] A --> D["__lt__ <"] A --> E["__le__ <="] A --> F["__gt__ >"] A --> G["__ge__ >="]
Simple Example
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
def __le__(self, other):
return self.score <= other.score
alice = Student("Alice", 90)
bob = Student("Bob", 85)
print(alice == bob) # False
print(alice > bob) # True (90 > 85)
print(bob < alice) # True
Magic Shortcut: @functools.total_ordering
Define just __eq__ and ONE of __lt__, __le__, __gt__, __ge__, and Python fills in the rest!
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, score):
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
➕ Chapter 3: Arithmetic Magic Methods — The Calculators
What’s the Story?
What if you could add two piggy banks together to see your total savings? Or multiply a recipe to serve more people? Arithmetic magic methods let objects do math!
The Math Operators
| Operator | Method | Example |
|---|---|---|
+ |
__add__ |
a + b |
- |
__sub__ |
a - b |
* |
__mul__ |
a * b |
/ |
__truediv__ |
a / b |
// |
__floordiv__ |
a // b |
% |
__mod__ |
a % b |
** |
__pow__ |
a ** b |
Simple Example: A Money Class
class Money:
def __init__(self, dollars):
self.dollars = dollars
def __add__(self, other):
return Money(self.dollars + other.dollars)
def __sub__(self, other):
return Money(self.dollars - other.dollars)
def __mul__(self, times):
return Money(self.dollars * times)
def __str__(self):
return f"${self.dollars}"
wallet = Money(50)
piggy = Money(30)
print(wallet + piggy) # $80
print(wallet - piggy) # $20
print(wallet * 2) # $100
Reverse Operations (__radd__, __rsub__, etc.)
When Python sees 5 + my_object, it first tries 5.__add__(my_object). If that fails, it tries my_object.__radd__(5).
def __radd__(self, other):
return Money(self.dollars + other)
# Now this works:
print(10 + wallet) # $60
📦 Chapter 4: Container Magic Methods — The Storage Boxes
What’s the Story?
Lists, dictionaries, and sets are containers. What if YOUR object could act like a container too? Store things, count them, and let people peek inside!
The Container Methods
graph LR A["Container Methods"] --> B["__len__ → len#40;#41;"] A --> C["__getitem__ → obj[key]"] A --> D["__setitem__ → obj[key] = val"] A --> E["__delitem__ → del obj[key]"] A --> F["__contains__ → &#39;x&#39; in obj"] A --> G["__iter__ → for x in obj"]
Simple Example: A Toy Box
class ToyBox:
def __init__(self):
self.toys = []
def __len__(self):
return len(self.toys)
def __getitem__(self, index):
return self.toys[index]
def __setitem__(self, index, toy):
self.toys[index] = toy
def __contains__(self, toy):
return toy in self.toys
def __iter__(self):
return iter(self.toys)
def add(self, toy):
self.toys.append(toy)
box = ToyBox()
box.add("Car")
box.add("Doll")
box.add("Ball")
print(len(box)) # 3
print(box[0]) # Car
print("Doll" in box) # True
for toy in box:
print(toy) # Car, Doll, Ball
📞 Chapter 5: __call__ — The Callable Object
What’s the Story?
Usually, you call functions. But what if your object could be called like a function? That’s __call__!
It’s like having a toy that does something special when you press its button (call it).
Simple Example
class Greeter:
def __init__(self, greeting):
self.greeting = greeting
def __call__(self, name):
return f"{self.greeting}, {name}!"
say_hello = Greeter("Hello")
say_hi = Greeter("Hi there")
print(say_hello("Alice")) # Hello, Alice!
print(say_hi("Bob")) # Hi there, Bob!
Real-World Use: Counters
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
click = Counter()
print(click()) # 1
print(click()) # 2
print(click()) # 3
💡 This is how decorators with state often work!
#️⃣ Chapter 6: __hash__ and __bool__ — Trust & Truth
__hash__ — The ID Card
When you put something in a set or use it as a dictionary key, Python needs a unique ID number (hash) for fast lookups.
graph TD A["Object"] --> B["__hash__#40;#41; → integer"] B --> C["Can be in sets"] B --> D["Can be dict key"]
Rules for __hash__:
- If two objects are equal (
__eq__), they must have the same hash - Hash should never change during the object’s life
- If you define
__eq__, you should also define__hash__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
p1 = Point(1, 2)
p2 = Point(1, 2)
points = {p1, p2}
print(len(points)) # 1 (they're equal!)
__bool__ — The Truth Teller
When Python asks “Is this object truthy or falsy?”, it calls __bool__.
class ShoppingCart:
def __init__(self):
self.items = []
def __bool__(self):
return len(self.items) > 0
def add(self, item):
self.items.append(item)
cart = ShoppingCart()
if cart:
print("Cart has items!")
else:
print("Cart is empty!") # This prints
cart.add("Apple")
if cart:
print("Cart has items!") # Now this prints
Default Behavior
Has __bool__? |
Has __len__? |
Result |
|---|---|---|
| Yes | - | Uses __bool__ |
| No | Yes | True if len() > 0 |
| No | No | Always True |
🎯 Summary: Your Magic Method Toolkit
| Category | Methods | Purpose |
|---|---|---|
| Display | __str__, __repr__ |
How object looks when printed |
| Compare | __eq__, __lt__, __gt__, etc. |
Compare two objects |
| Math | __add__, __sub__, __mul__, etc. |
Do arithmetic |
| Container | __len__, __getitem__, __iter__ |
Act like a list/dict |
| Callable | __call__ |
Use object like a function |
| Hash/Bool | __hash__, __bool__ |
Sets, dicts, and if checks |
🚀 You’re Now a Magic Method Wizard!
You’ve learned how to give your Python objects superpowers:
- They can introduce themselves nicely
- They can compare and compete
- They can do math together
- They can hold and organize data
- They can be called like functions
- They can be trusted in sets and answer yes/no questions
Go forth and create magical objects! 🪄✨
