Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on Pythonโs __str__
and __repr__
methods! ๐ In this guide, weโll explore how to make your objects talk โ literally showing you how they want to be displayed.
Have you ever wondered why some objects print nicely while others show cryptic memory addresses? ๐ค Thatโs where __str__
and __repr__
come in! These special methods let you control how your objects appear when printed or inspected.
By the end of this tutorial, youโll feel confident creating objects that communicate clearly with both developers and end users! Letโs dive in! ๐โโ๏ธ
๐ Understanding String Representation
๐ค What are str and repr?
Think of __str__
and __repr__
as your objectโs voice! ๐ฃ๏ธ Just like how you might introduce yourself differently at a party versus a job interview, objects need different ways to present themselves.
In Python terms:
- โจ
__str__
: The friendly, human-readable representation (like a name tag at a party) - ๐ง
__repr__
: The technical, unambiguous representation (like your resume at an interview) - ๐ฏ Both help objects describe themselves in string format
๐ก Why Use String Representation?
Hereโs why developers love proper string representation:
- Better Debugging ๐: See exactly whatโs in your objects
- Cleaner Output ๐: User-friendly messages and logs
- Professional Code ๐ผ: Makes your classes feel complete
- Easier Testing ๐งช: Compare objects more easily
Real-world example: Imagine a shopping cart ๐. Without string representation, youโd see <Cart object at 0x...>
. With it, you see Cart(3 items, total: $45.99)
!
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
# ๐ Hello, string representation!
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# ๐จ For users (str)
def __str__(self):
return f"{self.name} - ${self.price:.2f}"
# ๐ง For developers (repr)
def __repr__(self):
return f"Product(name='{self.name}', price={self.price})"
# ๐ฎ Let's try it!
laptop = Product("Gaming Laptop", 999.99)
print(str(laptop)) # Gaming Laptop - $999.99
print(repr(laptop)) # Product(name='Gaming Laptop', price=999.99)
๐ก Explanation: Notice how str()
gives a friendly format while repr()
shows exactly how to recreate the object!
๐ฏ Common Patterns
Here are patterns youโll use daily:
# ๐๏ธ Pattern 1: Default behavior
class SimpleClass:
pass
obj = SimpleClass()
print(obj) # <__main__.SimpleClass object at 0x...> ๐ฑ
# ๐จ Pattern 2: Only __str__
class FriendlyClass:
def __str__(self):
return "I'm friendly! ๐"
friendly = FriendlyClass()
print(friendly) # I'm friendly! ๐
print(repr(friendly)) # <__main__.FriendlyClass object at 0x...>
# ๐ Pattern 3: Both methods
class CompleteClass:
def __init__(self, value):
self.value = value
def __str__(self):
return f"Value: {self.value} โจ"
def __repr__(self):
return f"CompleteClass({self.value!r})"
๐ก Practical Examples
๐ Example 1: Shopping Cart System
Letโs build something real:
# ๐๏ธ Product with great representation
class Product:
def __init__(self, name, price, emoji="๐ฆ"):
self.name = name
self.price = price
self.emoji = emoji
def __str__(self):
return f"{self.emoji} {self.name}: ${self.price:.2f}"
def __repr__(self):
return f"Product({self.name!r}, {self.price}, {self.emoji!r})"
# ๐ Shopping cart with clear display
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, product, quantity=1):
self.items.append((product, quantity))
print(f"Added {quantity}x {product} to cart! โ
")
def __str__(self):
if not self.items:
return "๐ Your cart is empty"
lines = ["๐ Shopping Cart:"]
total = 0
for product, qty in self.items:
subtotal = product.price * qty
total += subtotal
lines.append(f" {qty}x {product} = ${subtotal:.2f}")
lines.append(f"๐ฐ Total: ${total:.2f}")
return "\n".join(lines)
def __repr__(self):
return f"ShoppingCart(items={self.items!r})"
# ๐ฎ Let's shop!
cart = ShoppingCart()
cart.add_item(Product("Python Book", 29.99, "๐"))
cart.add_item(Product("Coffee", 4.99, "โ"), 2)
cart.add_item(Product("Rubber Duck", 9.99, "๐ฆ"))
print("\n" + str(cart))
# ๐ Shopping Cart:
# 1x ๐ Python Book: $29.99 = $29.99
# 2x โ Coffee: $4.99 = $9.98
# 1x ๐ฆ Rubber Duck: $9.99 = $9.99
# ๐ฐ Total: $49.96
๐ฏ Try it yourself: Add a discount system that shows in the string representation!
๐ฎ Example 2: Game Character System
Letโs make it fun:
# ๐ Character with personality
class GameCharacter:
def __init__(self, name, level=1, health=100, class_type="Warrior"):
self.name = name
self.level = level
self.health = health
self.max_health = health
self.class_type = class_type
self.inventory = []
def take_damage(self, amount):
self.health = max(0, self.health - amount)
print(f"๐ฅ {self.name} took {amount} damage!")
def add_item(self, item):
self.inventory.append(item)
print(f"โจ {self.name} found {item}!")
def __str__(self):
# ๐จ Beautiful character display
health_bar = self._create_health_bar()
inventory_text = ", ".join(self.inventory) if self.inventory else "Empty"
return f"""
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ {self.name} the {self.class_type}
โ Level {self.level} {'โญ' * min(self.level, 5)}
โ Health: {health_bar} {self.health}/{self.max_health}
โ Items: {inventory_text}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"""
def _create_health_bar(self):
# ๐ฏ Visual health bar
percent = self.health / self.max_health
filled = int(percent * 10)
empty = 10 - filled
if percent > 0.6:
color = "๐ฉ"
elif percent > 0.3:
color = "๐จ"
else:
color = "๐ฅ"
return color * filled + "โฌ" * empty
def __repr__(self):
return (f"GameCharacter(name={self.name!r}, level={self.level}, "
f"health={self.health}, class_type={self.class_type!r})")
# ๐ฎ Create and play!
hero = GameCharacter("Pythonista", level=3, class_type="Wizard")
hero.add_item("๐ก๏ธ Sword of Debugging")
hero.add_item("๐ก๏ธ Shield of Type Safety")
hero.take_damage(30)
print(hero)
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Format Specifiers
When youโre ready to level up, use format specifiers:
# ๐ฏ Advanced formatting support
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __str__(self):
return f"{self.celsius}ยฐC"
def __repr__(self):
return f"Temperature({self.celsius})"
def __format__(self, spec):
# โจ Custom formatting magic!
if spec == 'f':
fahrenheit = self.celsius * 9/5 + 32
return f"{fahrenheit:.1f}ยฐF"
elif spec == 'k':
kelvin = self.celsius + 273.15
return f"{kelvin:.1f}K"
else:
return str(self)
# ๐ช Using the magic
temp = Temperature(25)
print(f"Default: {temp}") # 25ยฐC
print(f"Fahrenheit: {temp:f}") # 77.0ยฐF
print(f"Kelvin: {temp:k}") # 298.1K
๐๏ธ Advanced Topic 2: Container Classes
For the brave developers working with collections:
# ๐ Smart container representation
class TodoList:
def __init__(self, name):
self.name = name
self.todos = []
self.completed = []
def add_todo(self, task):
self.todos.append(task)
def complete_task(self, task):
if task in self.todos:
self.todos.remove(task)
self.completed.append(task)
def __str__(self):
output = [f"๐ {self.name}"]
if self.todos:
output.append("\n๐ Pending:")
for task in self.todos:
output.append(f" โฌ {task}")
if self.completed:
output.append("\nโ
Completed:")
for task in self.completed:
output.append(f" โ
{task}")
progress = len(self.completed) / (len(self.todos) + len(self.completed)) * 100 if (self.todos or self.completed) else 0
output.append(f"\n๐ Progress: {progress:.0f}%")
return "\n".join(output)
def __repr__(self):
return f"TodoList({self.name!r}, todos={self.todos!r}, completed={self.completed!r})"
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting repr
# โ Wrong way - only str, debugging is hard!
class BadClass:
def __init__(self, data):
self.data = data
def __str__(self):
return "Some object"
bad = BadClass([1, 2, 3])
print(bad) # Some object
print(repr(bad)) # <__main__.BadClass object at 0x...> ๐ฐ
# โ
Correct way - both methods!
class GoodClass:
def __init__(self, data):
self.data = data
def __str__(self):
return f"Object with {len(self.data)} items"
def __repr__(self):
return f"GoodClass({self.data!r})"
good = GoodClass([1, 2, 3])
print(good) # Object with 3 items
print(repr(good)) # GoodClass([1, 2, 3]) โจ
๐คฏ Pitfall 2: Infinite Recursion
# โ Dangerous - calling print inside __str__!
class RecursiveClass:
def __str__(self):
print("Don't do this!") # ๐ฅ Causes recursion!
return "Oops"
# โ
Safe - return strings only!
class SafeClass:
def __str__(self):
return "Safe and sound! ๐ก๏ธ"
def display(self):
print(self) # โ
Print outside __str__
๐ ๏ธ Best Practices
- ๐ฏ Always Define Both: Implement both
__str__
and__repr__
- ๐ Make repr Unambiguous:
eval(repr(obj))
should recreate the object when possible - ๐ก๏ธ Keep str User-Friendly: Focus on readability for end users
- ๐จ Use !r for Nested Objects:
{self.attr!r}
ensures proper repr formatting - โจ Donโt Print in Methods: Return strings, donโt print them
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Bank Account System
Create a banking system with great string representation:
๐ Requirements:
- โ Account class with balance and transaction history
- ๐ท๏ธ Different account types (Savings, Checking)
- ๐ค Account holder information
- ๐ Transaction timestamps
- ๐จ Beautiful account statement display!
๐ Bonus Points:
- Add currency formatting
- Show last 5 transactions in str
- Include account age in representation
๐ก Solution
๐ Click to see solution
from datetime import datetime
from collections import deque
# ๐ฏ Our banking system!
class BankAccount:
def __init__(self, holder_name, account_type="Checking", initial_balance=0):
self.holder_name = holder_name
self.account_type = account_type
self.balance = initial_balance
self.created_at = datetime.now()
self.transactions = deque(maxlen=10) # Keep last 10
if initial_balance > 0:
self._add_transaction("Initial deposit", initial_balance)
def deposit(self, amount):
if amount > 0:
self.balance += amount
self._add_transaction("Deposit", amount)
print(f"๐ฐ Deposited ${amount:.2f}")
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
self._add_transaction("Withdrawal", -amount)
print(f"๐ธ Withdrew ${amount:.2f}")
return True
print(f"โ Insufficient funds!")
return False
def _add_transaction(self, description, amount):
self.transactions.append({
'timestamp': datetime.now(),
'description': description,
'amount': amount,
'balance': self.balance
})
def __str__(self):
# ๐จ Beautiful account display
age_days = (datetime.now() - self.created_at).days
output = [
"โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
f"โ ๐ฆ {self.account_type} Account",
f"โ ๐ค {self.holder_name}",
f"โ ๐ฐ Balance: ${self.balance:,.2f}",
f"โ ๐
Account age: {age_days} days",
"โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโข"
]
if self.transactions:
output.append("โ ๐ Recent Transactions:")
for trans in list(self.transactions)[-5:]:
sign = "+" if trans['amount'] > 0 else ""
output.append(
f"โ {trans['timestamp'].strftime('%m/%d')} "
f"{trans['description']}: {sign}${abs(trans['amount']):.2f}"
)
else:
output.append("โ ๐ No transactions yet")
output.append("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
return "\n".join(output)
def __repr__(self):
return (f"BankAccount(holder_name={self.holder_name!r}, "
f"account_type={self.account_type!r}, "
f"initial_balance={self.balance})")
# ๐ฎ Test it out!
account = BankAccount("Python Developer", "Savings", 1000)
account.deposit(500)
account.withdraw(200)
account.deposit(1337)
account.withdraw(50)
print("\n" + str(account))
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ
Create readable objects with
__str__
and__repr__
๐ช - โ Debug effectively with proper object representation ๐ก๏ธ
- โ Build professional classes that communicate clearly ๐ฏ
- โ Avoid common mistakes like recursion and missing methods ๐
- โ Make beautiful output that users will love! ๐
Remember: Good string representation is like good communication - it makes everything clearer! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered string representation in Python!
Hereโs what to do next:
- ๐ป Practice with the banking exercise above
- ๐๏ธ Add string representation to your existing classes
- ๐ Move on to our next tutorial: Magic Methods Deep Dive
- ๐ Share your beautifully formatted objects with others!
Remember: Every time you implement __str__
and __repr__
, you make debugging easier for everyone. Keep coding, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ