# 05 -파이썬의 숨겨진 초능력: 특별한 (마법) 방법 설명

작성자

카테고리:

← 피드로
DEV Community · Thiruvengadam Sakthivel · 2026-08-05 개발(SW)

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             │
└──────────────────┴───────────────────────┴──────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

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')

Enter fullscreen mode Exit fullscreen mode

2. Sizing & Comparisons: __len__, __eq__, __lt__

  • __len__: Defines the behavior of len(obj). Must return a non-negative integer.
  • __eq__: Defines equality checks (==).
  • __lt__: Defines “less than” (<). Defining __lt__ automatically unlocks built-in sorting via sorted() 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)

Enter fullscreen mode Exit fullscreen mode

3. Iteration & Callables: __iter__, __call__

  • __iter__: Makes your object iterable, allowing it to be used in for loops, 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

Enter fullscreen mode Exit fullscreen mode

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 the with block. Its return value is bound to the target variable (as target).
  • __exit__: Executed when leaving the with block, 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

Enter fullscreen mode Exit fullscreen mode

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
            └─────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

Step 1: Initialize Your Workspace

uv init oop_day5 && cd oop_day5
touch smart_batch.py

Enter fullscreen mode Exit fullscreen mode

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}")

Enter fullscreen mode Exit fullscreen mode

Step 3: Run & Verify Execution

uv run smart_batch.py

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다