> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced OOP in Python: Metaclasses, Dunders & Frameworks

> Master Python's advanced OOP mechanics — abstract classes, metaclasses, dunder methods, dynamic class creation, and framework patterns.

Python's object model is far more dynamic than it first appears. Classes themselves are objects created at runtime by a **metaclass**, you can create classes programmatically without ever writing a `class` statement, and special **dunder methods** let you hook into nearly every built-in Python operation. These mechanics underpin modern frameworks like FastAPI, Pydantic, and SQLAlchemy. Understanding them turns "magic happens here" into clear, predictable behaviour you can reason about and extend.

## Abstract Base Classes (ABC)

An **Abstract Base Class** defines a set of methods that subclasses *must* implement. This enforces an API contract at class-instantiation time.

```python theme={null}
from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        """Subclasses must implement this method."""
        pass

class StripePayment(PaymentGateway):
    def process_payment(self, amount: float) -> bool:
        print(f"Processing Rs. {amount} via Stripe.")
        return True

# Attempting to instantiate the abstract class directly raises TypeError:
# gateway = PaymentGateway()
# TypeError: Can't instantiate abstract class PaymentGateway
```

<Note>
  `ABC` and `@abstractmethod` come from Python's built-in `abc` module. A class with at least one unimplemented abstract method **cannot** be instantiated.
</Note>

## Classes are Objects — Metaclasses

In Python, every class is itself an object, and that object was created by a **metaclass**. By default, all classes are instances of the built-in metaclass `type`:

```python theme={null}
class MyClass:
    pass

print(type(MyClass))   # <class 'type'>
```

### Dynamic Class Creation with `type()`

Because `type` is a callable, you can create classes programmatically using its three-argument form:

```python theme={null}
# type(name, bases_tuple, attributes_dict)
DynamicUser = type("DynamicUser", (object,), {
    "role": "Guest",
    "say_hello": lambda self: f"Hello, I am a {self.role}!"
})

user = DynamicUser()
print(user.say_hello())   # Hello, I am a Guest!
```

This is exactly what Python does internally when it processes a `class` statement.

## Special Attributes: `__dict__` and `__annotations__`

### `__dict__`

Every class and instance has a `__dict__` — a namespace dictionary containing its attributes and methods:

```python theme={null}
class User:
    species = "Human"

    def __init__(self, name):
        self.name = name

u = User("Amit")
print(u.__dict__)     # {'name': 'Amit'}          — instance attributes
print(User.__dict__)  # includes 'species', '__init__', etc.
```

### `__annotations__`

Type hints defined on a class are stored in `__annotations__`. Libraries like Pydantic and dataclasses read this dictionary at runtime to drive validation and code generation:

```python theme={null}
class Profile:
    username: str
    age: int

print(Profile.__annotations__)
# {'username': <class 'str'>, 'age': <class 'int'>}
```

## Dunder Methods (Magic Methods)

Dunder methods let you hook into Python's built-in operations and protocols.

| Method     | Purpose                                                              |
| ---------- | -------------------------------------------------------------------- |
| `__new__`  | Allocates memory and returns a new instance (runs before `__init__`) |
| `__init__` | Initialises the instance returned by `__new__`                       |
| `__call__` | Makes an instance callable like a function                           |
| `__repr__` | Developer-facing string representation                               |
| `__str__`  | User-facing string representation                                    |
| `__eq__`   | Custom equality comparison (`==`)                                    |

```python theme={null}
class CallableLogger:
    def __new__(cls, *args, **kwargs):
        print("1. Memory allocated via __new__")
        return super().__new__(cls)

    def __init__(self, prefix):
        print("2. Instance initialised via __init__")
        self.prefix = prefix

    def __call__(self, message):
        print(f"[{self.prefix}] {message}")

logger = CallableLogger("SYSTEM")
# 1. Memory allocated via __new__
# 2. Instance initialised via __init__

logger("Service started")
# [SYSTEM] Service started
```

## Class Creation Stages

When Python executes a `class` statement, it follows these steps:

<Steps>
  <Step title="Compile the class body">
    Python compiles the class body into a namespace dictionary.
  </Step>

  <Step title="Identify the metaclass">
    Python determines which metaclass to use (default: `type`).
  </Step>

  <Step title="Metaclass __new__ runs">
    The metaclass allocates and constructs the class object.
  </Step>

  <Step title="Metaclass __init__ runs">
    The metaclass configures and registers the class object.
  </Step>

  <Step title="Class is bound to its name">
    The class object is assigned to the name in the current scope.
  </Step>
</Steps>

## Real-World Framework Implementations

Advanced OOP mechanics are the backbone of modern Python frameworks.

### SQLAlchemy — Declarative ORM

SQLAlchemy uses metaclasses to automatically map Python class attributes to database columns. When your class inherits from `DeclarativeBase`:

* The metaclass reads class attributes like `id = Column(Integer)`.
* It uses the class name to register a database table.
* Attribute access like `user.name = "Rahul"` becomes a tracked database operation.

### FastAPI / Pydantic — Data Validation

Pydantic uses metaclasses and `__annotations__` for automatic serialisation and validation:

* When a class inherits from `BaseModel`, Pydantic's metaclass reads `__annotations__` at class-definition time.
* It builds validators for each type hint (e.g., `age: int` generates an integer coercion function).
* FastAPI passes incoming HTTP request data through these validators before your endpoint function ever runs, providing clear error messages for invalid input.

<Accordion title="Practical Example: Pydantic reads __annotations__">
  ```python theme={null}
  from pydantic import BaseModel

  class User(BaseModel):
      username: str
      age: int

  # Pydantic reads User.__annotations__ = {'username': str, 'age': int}
  # and generates validators for each field.

  u = User(username="alice", age="25")  # "25" is coerced to int
  print(u.age)         # 25
  print(type(u.age))   # <class 'int'>
  ```
</Accordion>
