Welcome to Day 5! Special methods (also known as magic methods or dunder methods, short for "double underscore") allow your custom Python classes to hook into Python's built-in operators, syntax, and runtime protocols.
By implementing dunder methods, you make your custom objects act like built-in Python types (list, dict, int), enabling features like pretty printing, iteration, comparison, context management, and direct execution.
1. Categorizing Core Dunder Methods ποΈ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SPECIAL (MAGIC) METHODS β
ββββββββββββββββββββ¬ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ€
β Category β Dunder Method β Triggering Syntax / Operation β
ββββββββββββββββββββΌββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Representation β __str__(self) β str(obj), print(obj), f"{obj}" β
β β __repr__(self) β repr(obj), REPL output, debuggingβ
ββββββββββββββββββββΌββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Sizing & Order β __len__(self) β len(obj) β
β β __eq__(self, other) β obj1 == obj2 β
β β __lt__(self, other) β obj1 < obj2, sorted([obj1, obj2])β
ββββββββββββββββββββΌββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Protocols β __iter__(self) β for item in obj: β
β β __call__(self, *args) β obj(*args) (callable instance) β
ββββββββββββββββββββΌββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Context Manager β __enter__(self) β with obj as resource: β
β β __exit__(...) β Exiting a with block β
ββββββββββββββββββββ΄ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ
2. Topic Breakdown π‘
1. Object Representation: __str__ vs __repr__
-
__str__: Returns a friendly, human-readable string representation intended for end-users (print(), f-strings). -
__repr__: Returns an unambiguous representation intended for developers and debugging. Ideally, it looks like valid Python code to recreate the object.
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
def __str__(self) -> str:
return f"'{self.title}' by {self.author}"
def __repr__(self) -> str:
return f"Book(title={self.title!r}, author={self.author!r})"
b = Book("Designing Data-Intensive Applications", "Martin Kleppmann")
print(str(b)) # 'Designing Data-Intensive Applications' by Martin Kleppmann
print(repr(b)) # Book(title='Designing Data-Intensive Applications', author='Martin Kleppmann')
2. Sizing & Comparisons: __len__, __eq__, __lt__
-
__len__: Defines the behavior oflen(obj). Must return a non-negative integer. -
__eq__: Defines equality checks (==). -
__lt__: Defines "less than" (<). Defining__lt__automatically unlocks built-in sorting viasorted()or.sort().
class Task:
def __init__(self, title: str, priority: int):
self.title = title
self.priority = priority
def __eq__(self, other: object) -> bool:
if not isinstance(other, Task):
return NotImplemented
return self.priority == other.priority
def __lt__(self, other: 'Task') -> bool:
return self.priority < other.priority
t1 = Task("Fix Critical Bug", priority=1)
t2 = Task("Update Docs", priority=3)
print(t1 < t2) # True (1 < 3)
3. Iteration & Callables: __iter__, __call__
-
__iter__: Makes your object iterable, allowing it to be used inforloops, list comprehensions, and unpacked with*. -
__call__: Allows an instance of a class to be invoked like a function.
class FactorialCalculator:
def __call__(self, n: int) -> int:
"""Invoked when object is called like a function: calc(5)"""
result = 1
for i in range(1, n + 1):
result *= i
return result
calc = FactorialCalculator()
print(calc(5)) # Output: 120
4. Context Management: __enter__, __exit__
Enables the use of with statements to manage setup and teardown operations safely (e.g., file handling, locking, transaction management).
-
__enter__: Executed when entering thewithblock. Its return value is bound to the target variable (as target). -
__exit__: Executed when leaving thewithblock, even if an exception occurs.
class TimerContext:
import time
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.perf_counter() - self.start
print(f"β±οΈ Block executed in {elapsed:.4f} seconds")
return False # Do not suppress exceptions if any occurred
3. Practice Challenge: Custom SmartBatch Pipeline π
Let's combine all 9 magic methods into a single production-ready data structure: a SmartBatch manager that acts as an iterable, comparable, callable context manager for tasks.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SMART BATCH ENGINE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[ with SmartBatch("Data Ingestion") as batch: ]
β
ββββββββββββββββββ΄βββββββββββββββββ
β __enter__() Called β --> Opens Processing Window
ββββββββββββββββββ¬βββββββββββββββββ
β
ββββββββββββββββββ΄βββββββββββββββββ
β Batch Populated & Filtered β
β β’ __len__() --> Size Check β
β β’ __call__() --> Filtering β
β β’ __iter__() --> Unpacking β
β β’ __lt__() --> Sorting β
ββββββββββββββββββ¬βββββββββββββββββ
β
ββββββββββββββββββ΄βββββββββββββββββ
β __exit__() Called β --> Validates & Flushes Batch
βββββββββββββββββββββββββββββββββββ
Step 1: Initialize Your Workspace
uv init oop_day5 && cd oop_day5
touch smart_batch.py
Step 2: Implement smart_batch.py
# smart_batch.py
from typing import List, Iterator, Any, Optional, Self
import time
class DataTask:
"""Represents an individual unit of work in the batch system."""
def __init__(self, task_id: str, payload: str, priority: int):
self.task_id = task_id
self.payload = payload
self.priority = priority # Lower number = higher priority (1 is top priority)
def __str__(self) -> str:
"""User-friendly representation."""
return f"[{self.task_id}] {self.payload} (P{self.priority})"
def __repr__(self) -> str:"""Developer debugging representation."""
return f"DataTask(task_id={self.task_id!r}, payload={self.payload!r}, priority={self.priority})"
def __eq__(self, other: object) -> bool:"""Equality based on task ID and priority."""
if not isinstance(other, DataTask):
return NotImplemented
return self.task_id == other.task_id and self.priority == other.priority
def __lt__(self, other: 'DataTask') -> bool:"""Enables native sorting based on priority."""
return self.priority < other.priority
class SmartBatch:
"""A custom container demonstrating all key Python magic methods."""
def __init__(self, name: str):
self.name = name
self.tasks: List[DataTask] = []
self.is_active = False
self._start_time: float = 0.0
# 1. REPRESENTATION METHODS
def __str__(self) -> str:
return f"SmartBatch('{self.name}') containing {len(self.tasks)} task(s)"
def __repr__(self) -> str:
return f"SmartBatch(name={self.name!r}, tasks={self.tasks!r})"
# 2. SIZING & COMPARISON METHODS
def __len__(self) -> int:
"""Returns the number of tasks in the batch."""
return len(self.tasks)
def __eq__(self, other: object) -> bool:
"""Batches are equal if they share the same name and task count."""
if not isinstance(other, SmartBatch):
return NotImplemented
return self.name == other.name and len(self) == len(other)
def __lt__(self, other: 'SmartBatch') -> bool:
"""Allows batches to be compared/sorted by task count."""
return len(self) < len(other)
# 3. PROTOCOL METHODS
def __iter__(self) -> Iterator[DataTask]:
"""Allows 'for task in batch:' iteration."""
return iter(self.tasks)
def __call__(self, max_priority: int) -> List[DataTask]:
"""Makes the batch instance callable to filter tasks on the fly!
Example: filtered_tasks = batch(max_priority=2)
"""
return [task for task in self.tasks if task.priority <= max_priority]
# 4. CONTEXT MANAGER METHODS
def __enter__(self) -> Self:
"""Opens processing window for 'with SmartBatch(...) as batch:'."""
self.is_active = True
self._start_time = time.perf_counter()
print(f"π’ [__enter__] Context Opened: Batch '{self.name}' processing started.")
return self
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
"""Flushes tasks and handles cleanup automatically on block exit."""
elapsed = (time.perf_counter() - self._start_time) * 1000
self.is_active = False
if exc_type:
print(f"β [__exit__] Batch processing failed with exception: {exc_val}")
return False # Propagate exception
print(f"π [__exit__] Context Closed: Flushed {len(self)} tasks in {elapsed:.2f}ms.")
return True # Exception handled cleanly
# ==========================================
# TEST EXECUTION SUITE
# ==========================================
if __name__ == "__main__":
print("--- 1. Testing Representation & Equality ---")
t1 = DataTask("T-101", "Process Order #882", priority=1)
t2 = DataTask("T-102", "Send Confirmation Email", priority=3)
t3 = DataTask("T-103", "Sync Analytics", priority=2)
print(f"__str__ : {t1}")
print(f"__repr__ : {repr(t1)}")
print(f"__eq__ : t1 == t2 -> {t1 == t2}")
print("\n--- 2. Testing Context Manager & Batch Operations ---")
# Using __enter__ and __exit__ via 'with' statement
with SmartBatch("Nightly ETL Pipeline") as batch:
# Add tasks to batch
batch.tasks.extend([t1, t2, t3])
# Testing __len__
print(f"\nBatch Length (__len__): {len(batch)}")
print(f"Batch Description (__str__): {batch}")
# Testing __iter__
print("\nIterating through batch tasks (__iter__):")
for task in batch:
print(f" ββ {task}")
# Testing __call__ (Filtering by priority)
print("\nFiltering batch using instance call (__call__ for max_priority <= 2):")
high_priority_tasks = batch(max_priority=2)
for hp_task in high_priority_tasks:
print(f" π₯ High Priority: {hp_task}")
# Testing sorting (__lt__ on DataTask)
print("\nSorting tasks in-place using __lt__:")
batch.tasks.sort()
for sorted_task in batch:
print(f" β Sorted: {sorted_task}")
print("\n--- 3. Testing Batch Comparisons (__lt__ & __eq__) ---")
batch_a = SmartBatch("Batch A")
batch_b = SmartBatch("Batch B")
batch_a.tasks.append(t1)
batch_b.tasks.extend([t1, t2, t3])
print(f"batch_a length: {len(batch_a)}, batch_b length: {len(batch_b)}")
print(f"batch_a < batch_b (__lt__) : {batch_a < batch_b}")
print(f"batch_a == batch_b (__eq__): {batch_a == batch_b}")
Step 3: Run & Verify Execution
uv run smart_batch.py
Output Summary
--- 1. Testing Representation & Equality ---
__str__ : [T-101] Process Order #882 (P1)
__repr__ : DataTask(task_id='T-101', payload='Process Order #882', priority=1)
__eq__ : t1 == t2 -> False
--- 2. Testing Context Manager & Batch Operations ---
π’ [__enter__] Context Opened: Batch 'Nightly ETL Pipeline' processing started.
Batch Length (__len__): 3
Batch Description (__str__): SmartBatch('Nightly ETL Pipeline') containing 3 task(s)
Iterating through batch tasks (__iter__):
ββ [T-101] Process Order #882 (P1)
ββ [T-102] Send Confirmation Email (P3)
ββ [T-103] Sync Analytics (P2)
Filtering batch using instance call (__call__ for max_priority <= 2):
π₯ High Priority: [T-101] Process Order #882 (P1)
π₯ High Priority: [T-103] Sync Analytics (P2)
Sorting tasks in-place using __lt__:
β Sorted: [T-101] Process Order #882 (P1)
β Sorted: [T-103] Sync Analytics (P2)
β Sorted: [T-102] Send Confirmation Email (P3)
π [__exit__] Context Closed: Flushed 3 tasks in 0.15ms.
--- 3. Testing Batch Comparisons (__lt__ & __eq__) ---
batch_a length: 1, batch_b length: 3
batch_a < batch_b (__lt__) : True
batch_a == batch_b (__eq__): False
Top comments (0)