Imagine: you print a zombie to the console and see: <__main__.Zombie object at 0x10e8c4d90> 😱
Useless! You want to see the name, health, status. That’s exactly what the magic method __str__ is for ✨
🤔 The problem: ugly output
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
walker = Zombie("Walker", 50)
print(walker)
# <__main__.Zombie object at 0x10e8c4d90> ← Useless!
What’s wrong?
Python doesn’t know how to display your class. It falls back to showing the memory address.
✨ The fix: __str__
__str__ is a magic method called by print(object) or str(object).
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
def __str__(self): # ← Magic method!
return f"Zombie '{self.name}' (HP: {self.health})"
walker = Zombie("Walker", 50)
print(walker)
# Zombie 'Walker' (HP: 50) ← Clean! ✨
What changed?
- Added the __str__ method
- It returns a string (return is required)
- Python automatically calls it when you print() the object
🎨 Examples of nice output
Example 1: Zombie with status indicator
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
def __str__(self):
status = "🟢" if self.health > 30 else "🔴"
return f"{status} Zombie '{self.name}' | HP: {self.health}"
walker = Zombie("Walker", 50)
runner = Zombie("Runner", 20)
print(walker) # 🟢 Zombie 'Walker' | HP: 50
print(runner) # 🔴 Zombie 'Runner' | HP: 20
Example 2: Human with inventory
class Human:
def __init__(self, name, health):
self.name = name
self.health = health
self.ammo = 12
self.kills = 0
def __str__(self):
return (f"👤 {self.name}\n"
f" ❤️ HP: {self.health}/100\n"
f" 🔫 Ammo: {self.ammo}\n"
f" 💀 Kills: {self.kills}")
rick = Human("Rick", 85)
rick.kills = 3
print(rick)
# 👤 Rick
# ❤️ HP: 85/100
# 🔫 Ammo: 12
# 💀 Kills: 3
Example 3: Weapon
class Weapon:
def __init__(self, name, damage, ammo):
self.name = name
self.damage = damage
self.ammo = ammo
def __str__(self):
return f"🔫 {self.name} (Damage: {self.damage}, Ammo: {self.ammo})"
pistol = Weapon("Pistol", 20, 12)
rifle = Weapon("Rifle", 50, 30)
print(pistol) # 🔫 Pistol (Damage: 20, Ammo: 12)
print(rifle) # 🔫 Rifle (Damage: 50, Ammo: 30)
🔄 Ways to use __str__
1. With print()
walker = Zombie("Walker", 50)
print(walker) # Automatically calls walker.__str__()
2. With str()
walker = Zombie("Walker", 50)
text = str(walker) # Calls walker.__str__()
print(text) # Zombie 'Walker' (HP: 50)
3. Inside f-strings
walker = Zombie("Walker", 50)
message = f"A {walker} appeared!"
print(message)
# A Zombie 'Walker' (HP: 50) appeared!
4. In lists
zombies = [
Zombie("Walker", 50),
Zombie("Runner", 30),
Zombie("Tank", 100)
]
for zombie in zombies:
print(zombie)
# Zombie 'Walker' (HP: 50)
# Zombie 'Runner' (HP: 30)
# Zombie 'Tank' (HP: 100)
📋 Rules for __str__
✅ Do this:
1. Always return a string
def __str__(self):
return f"Zombie '{self.name}'" # ✅ A string
2. Keep it short and informative
def __str__(self):
return f"{self.name} (HP: {self.health})" # ✅ Clear
3. Make it human-readable
def __str__(self):
return f"👤 {self.name} | ❤️ {self.health}" # ✅ Friendly
❌ Avoid this:
1. Using print instead of return
def __str__(self):
print(f"Zombie '{self.name}'") # ❌ print instead of return!
# Returns None!
2. Returning a non-string
def __str__(self):
return self.health # ❌ An integer, not a string!
3. Dumping too much data
def __str__(self):
return f"""
Name: {self.name}
Health: {self.health}
Speed: {self.speed}
Created: {self.created_at}
Last updated: {self.updated_at}
...50 more lines...
""" # ❌ Way too much!
🪄 Other magic methods
__str__ is not the only magic method — there are many:
__repr__ — for debugging
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
def __str__(self):
return f"Zombie '{self.name}'" # For the end user
def __repr__(self):
return f"Zombie(name='{self.name}', health={self.health})" # For the developer
walker = Zombie("Walker", 50)
print(walker) # Zombie 'Walker' ← calls __str__()
print(repr(walker)) # Zombie(name='Walker', health=50) ← calls __repr__()
The difference:
- __str__ — pretty output for the user
- __repr__ — precise representation for the developer (should be unambiguous)
__len__ — for len()
class Horde:
def __init__(self):
self.zombies = []
def add(self, zombie):
self.zombies.append(zombie)
def __len__(self): # ← For len()
return len(self.zombies)
horde = Horde()
horde.add(Zombie("Walker", 50))
horde.add(Zombie("Runner", 30))
print(len(horde)) # 2 ← calls horde.__len__()
__eq__ — for == comparison
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
def __eq__(self, other): # ← For ==
return self.name == other.name and self.health == other.health
z1 = Zombie("Walker", 50)
z2 = Zombie("Walker", 50)
z3 = Zombie("Runner", 30)
print(z1 == z2) # True ← calls z1.__eq__(z2)
print(z1 == z3) # False
Common magic methods at a glance:
| Method | Triggered by | Purpose |
|---|---|---|
__init__ |
Zombie() |
Object creation |
__str__ |
print(obj), str(obj) |
User-friendly string |
__repr__ |
repr(obj) |
Debug-friendly string |
__len__ |
len(obj) |
Object length |
__eq__ |
obj1 == obj2 |
Equality check |
__lt__ |
obj1 < obj2 |
Less-than comparison |
__add__ |
obj1 + obj2 |
Addition |
__getitem__ |
obj[index] |
Index access |
(More to come in later lessons)
💡 When to use __str__?
✅ Use it when:
- You need to print the object to the console
- You want to debug your code (inspect object state)
- You’re building a game (displaying characters, items)
- You’re writing logs (recording object state)
Example: debugging a battle
class Zombie:
def __init__(self, name, health):
self.name = name
self.health = health
def __str__(self):
return f"Zombie '{self.name}' (HP: {self.health})"
def take_damage(self, damage):
self.health -= damage
print(f"{self} took {damage} damage")
# ↑ Uses __str__!
walker = Zombie("Walker", 50)
walker.take_damage(20)
# Zombie 'Walker' (HP: 30) took 20 damage ← Clean output!
⚠️ Common mistakes
Mistake 1: print instead of return
class Zombie:
def __str__(self):
print(f"Zombie '{self.name}'") # ❌ print!
walker = Zombie("Walker", 50)
print(walker)
# Zombie 'Walker'
# None ← returned None!
Fix:
def __str__(self):
return f"Zombie '{self.name}'" # ✅ return
Mistake 2: Returning a non-string
class Zombie:
def __str__(self):
return self.health # ❌ Integer!
walker = Zombie("Walker", 50)
print(walker) # TypeError: __str__ returned non-string
Fix:
def __str__(self):
return str(self.health) # ✅ Convert to string
# Or
return f"HP: {self.health}" # ✅ f-string
Mistake 3: Forgot self
class Zombie:
def __str__(): # ❌ No self!
return "Zombie"
walker = Zombie("Walker", 50)
print(walker) # TypeError
Fix:
def __str__(self): # ✅ Added self
📝 Checklist
- [ ] I know why
__str__is needed - [ ] I understand that
__str__must return a string - [ ] I can define
__str__in a class - [ ] I know that
__str__is called byprint() - [ ] I understand the difference between
__str__and__repr__ - [ ] I can create nice output with emoji and formatting
🚀 Summary
__str__ is a magic method for pretty-printing objects.
Syntax:
class ClassName:
def __str__(self):
return "string describing the object"
Usage:
print(object) # Calls __str__()
str(object) # Calls __str__()
f"{object}" # Calls __str__()
Rules:
- ✅ Always returns a string (return, not print)
- ✅ Short and informative
- ✅ Human-readable
Other magic methods:
- __repr__ — for debugging (precise representation)
- __len__ — for len(object)
- __eq__ — for object1 == object2
Why bother?
Pretty output simplifies debugging and makes your code easier to understand! 🎨
Next step: Learn how to persist data to files! 💾
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!