Magic & Dunder Methods
In Python, methods that start and end with double underscores—like __init__—are called Dunder Methods (short for Double Underscore) or Magic Methods.
Dunder methods are Python's internal hook mechanism. They allow your custom objects to integrate seamlessly with built-in functions like print(), len(), str(), and mathematical operators like + or ==.
1. Learning Objective
By the end of this lesson, you will understand:
- What Dunder Methods are and how Python triggers them automatically.
- How to override
__str__to display human-readable object descriptions. - The difference between
__str__(for end users) and__repr__(for developers/debugging). - How to customize
len(obj)using__len__andobj1 == obj2using__eq__.
2. Why This Concept Exists
If you create a custom Book object and print it without defining a dunder method, Python outputs an ugly, unreadable memory string:
b1 = Book("Python Mastery", 350)
print(b1) # Output: <__main__.Book object at 0x0000021F7B8E07F0>
Similarly, trying to run len(b1) or b1 + b2 throws a TypeError.
Python uses Dunder Methods so you can teach your custom objects how to behave when interacted with using standard Python operators.
3. Common Beginner Confusions
Confusion 1: "What is the difference between __str__ and __repr__?"
__str__(String): Intended to produce a clean, friendly text description for end-users (triggered byprint(obj)orstr(obj)).__repr__(Representation): Intended to produce an unambiguous, developer-focused description for debugging and logs (triggered in terminal REPL orrepr(obj)).
Confusion 2: "Do I call b1.__str__() directly in code?"
- Answer: NO! You should rarely call dunder methods directly. Instead, use Python's clean built-in syntax
print(b1)orstr(b1). Python calls__str__()automatically under the hood.
4. Mental Model: Universal Translator
Think of Dunder Methods as a Universal Plug-and-Play Adapter:
Imagine buying a device from overseas. If it has a proprietary 3-prong plug, your wall outlet cannot accept it.
Dunder methods act as standard adapters:
- When
print()plugs into your object, it asks: "Do you implement the__str__adapter?" - When
len()plugs into your object, it asks: "Do you implement the__len__adapter?"
5. Internal Python Execution Flow
Let's trace what happens when you execute print(b1):
Code Executed: print(b1)
↓ Execution Steps:
1. Function print() receives argument b1 (Address 0x9000).
2. print() invokes built-in str(b1).
3. str(b1) looks up the class of b1 -> 'Book'.
4. Python checks if '__str__' exists inside class 'Book'.
5. If found -> Python executes Book.__str__(b1) -> returns "Book: Python Mastery (350 pages)".
6. If NOT found -> Python falls back to default object.__repr__() -> outputs "<__main__.Book object at 0x9000>".
6. Memory Visualization: Operator Hook Interception
Internal Protocol Hook Interception
print(b1)
DUNDER HOOK
b1.str()
"Book: Python Mastery"
7. Python Code Implementation
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
# 1. Human-Readable User String
def __str__(self):
return f"Book: '{self.title}' ({self.pages} pages)"
# 2. Developer Debug Representation
def __repr__(self):
return f"Book(title='{self.title}', pages={self.pages})"
# 3. Custom Length Protocol
def __len__(self):
return self.pages
# 4. Custom Equality Comparison Protocol (==)
def __eq__(self, other):
if isinstance(other, Book):
return self.title == other.title and self.pages == other.pages
return False
# --- Execution ---
b1 = Book("Python Mastery", 350)
b2 = Book("Python Mastery", 350)
b3 = Book("Data Science Fundamentals", 420)
# Triggers __str__
print(b1)
# Triggers __len__
print("Book page count via len():", len(b1))
# Triggers __eq__
print("Is b1 equal to b2?", b1 == b2)
print("Is b1 equal to b3?", b1 == b3)
# Triggers __repr__
print("Developer Repr:", repr(b1))
Output:
Book: 'Python Mastery' (350 pages)
Book page count via len(): 350
Is b1 equal to b2? True
Is b1 equal to b3? False
Developer Repr: Book(title='Python Mastery', pages=350)
8. Line-by-Line Execution Analysis
Line: def __str__(self):
- Purpose: Overrides the default string conversion protocol.
- Return requirement: MUST return a string. Returning an integer or list raises a
TypeError.
Line: def __len__(self):
- Purpose: Overrides built-in
len(obj). - Return requirement: MUST return a non-negative integer representing the length or size.
Line: b1 == b2
- Translation: Python transforms
b1 == b2intob1.__eq__(b2). - Execution: Passes
b1asselfandb2asother. Compares titles and pages. ReturnsTrue.
9. Essential Dunder Methods Reference Table
| Dunder Method | Syntax Trigger | Purpose | Example |
|---|---|---|---|
__init__(self, ...) | Obj(...) | Object Initialization | Setting attributes on creation |
__str__(self) | print(obj), str(obj) | User text string | Friendly formatted description |
__repr__(self) | repr(obj), terminal REPL | Developer debug string | Unambiguous object code representation |
__len__(self) | len(obj) | Collection / size length | Page count, playlist size |
__eq__(self, other) | obj1 == obj2 | Equality comparison | Comparing object attribute values |
__add__(self, other) | obj1 + obj2 | Addition operator | Combining two custom objects |
10. Illustration Prompt for Diagram Generation
Excalidraw Diagram Prompt: A hand-drawn diagram illustrating Python Dunder Methods as Protocol Hooks. Draw 3 built-in Python call boxes on the left: "print(b1)", "len(b1)", "b1 == b2". Draw 3 dashed arrows leading to a central Class Box labeled "Class Book (Protocol Implementation)". Inside the Class Box, show 3 hook receptors: "str -> returns formatted string", "len -> returns self.pages", "eq -> compares titles". Draw output arrows pointing to the right showing the final results. White background, clean hand-drawn look, minimal color scheme (Blue, Purple, Green).
Quick Summary
- Dunder Methods (
__name__) are special hook methods that bind custom objects to Python built-in syntax. __str__provides user-friendly text descriptions (print()).__repr__provides developer-focused debug representations.__len__and__eq__customizelen()and==operator behaviors.