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

# Classes & OOP

> Blueprint class definitions, inheritance, encapsulation, class/static methods, and properties in Python

## 1. Classes and Instances

Object-Oriented Programming (OOP) is a paradigm that groups related data (attributes) and behaviors (methods) into reusable blueprints called **classes**. An individual object built from a class is an **instance**.

A class is a blueprint for creating objects. In this example, we create a simple `Student` class with two attributes (`name` and `age`) and two methods (`introduce()` and `study()`).

```python theme={null}
# Blueprint class definition

class Student:
    # Constructor
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Method
    def study(self):
        print(f"{self.name} is studying.")

    # Method
    def introduce(self):
        print(f"My name is {self.name}.")
        print(f"I am {self.age} years old.")


# Create an object
student1 = Student("Alice", 20)

# Access attributes
print(student1.name)
print(student1.age)

# Call methods
student1.introduce()
student1.study()
```

### Output

```text theme={null}
Alice
20
My name is Alice.
I am 20 years old.
Alice is studying.
```

## Key Concepts

* **Class**: `Student`
* **Object**: `student1`
* **Attributes**: `name`, `age`
* **Methods**: `introduce()`, `study()`
* **Constructor**: `__init__()` initializes the object's attributes.

This example demonstrates:

* Defining a class using `class`
* Initializing attributes using `__init__()`
* Creating methods inside a class
* Creating an object
* Accessing object attributes
* Calling object methods

***

## 2. Attributes and Methods

Classes can hold both data and code. These are categorized into instance-level and class-level members.

### Instance Methods

Functions inside a class that operate on a specific instance of that class. They must accept `self` as the first argument.

```python theme={null}
class Account:
    def __init__(self, owner: str, balance: float):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount: float):
        self.balance += amount
        return self.balance
```

### Class Attributes & Methods (`@classmethod`)

* **Class Attributes**: Variables shared by all instances of a class.
* **Class Methods**: Decorated with `@classmethod`, they accept `cls` (the class itself) instead of `self`. They are used for factory methods or modifying class-level state.

```python theme={null}
class Account:
    bank_name = "State Bank of India"  # Class attribute

    def __init__(self, owner: str, balance: float):
        self.owner = owner
        self.balance = balance

    @classmethod
    def update_bank(cls, new_name: str):
        cls.bank_name = new_name  # Modifies class-level attribute
```

### Static Methods (`@staticmethod`)

Decorated with `@staticmethod`, these do not accept `self` or `cls`. They act like normal helper functions grouped inside the class namespace.

```python theme={null}
class Account:
    @staticmethod
    def is_valid_amount(amount: float) -> bool:
        return amount > 0
```

### Type Checking (`type` vs `isinstance`)

* `type(obj)` returns the exact class/type of an object.
* `isinstance(obj, ClassName)` checks if an object is an instance of a class or any subclass (preferred for polymorphism).

```python theme={null}
class Animal: pass
class Dog(Animal): pass

buddy = Dog()

print(type(buddy) is Dog)       # True
print(type(buddy) is Animal)    # False (checks exact class)
print(isinstance(buddy, Animal)) # True (checks inheritance hierarchy)
```

***

## 3. Class Inheritance

Inheritance allows a new class (child/subclass) to inherit attributes and methods from an existing class (parent/superclass).

```python theme={null}
class Animal:
    def __init__(self, name: str):
        self.name = name

    def eat(self):
        return f"{self.name} is eating"

# Dog inherits from Animal
class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)  # Initialize parent attributes
        self.breed = breed

    def bark(self):
        return "Woof!"

buddy = Dog("Buddy", "Golden Retriever")
print(buddy.eat())  # Buddy is eating (inherited method)
print(buddy.bark()) # Woof!
```

### Passing Arguments Along with Class Name in Inheritance

You can pass custom configuration arguments directly inside the class declaration parentheses next to the parent class name. This is done by implementing the special class method `__init_subclass__` in the parent class.

```python theme={null}
class DatabaseModel:
    table_name: str

    # Hook called when a subclass is defined
    def __init_subclass__(cls, table: str, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.table_name = table

# Passing the 'table' argument directly with the class name
class UserProfile(DatabaseModel, table="users"):
    pass

print(UserProfile.table_name)  # Output: users
```

#### What happens when this code is run?

1. **Class-level Interception**: When Python compiles and executes the class definition of `UserProfile`, it notices that `DatabaseModel` defines a `__init_subclass__` method.
2. **Hook Execution**: Instead of just creating the `UserProfile` class normally, Python automatically calls `DatabaseModel.__init_subclass__(UserProfile, table="users")`.
3. **Property Assignment**: The method receives the newly created subclass as `cls` and the keyword argument `table`. It then sets `cls.table_name = "users"`. This operates at **class-definition time** (import time), long before any instances of `UserProfile` are created.

#### Why do we need this pattern?

* **Declarative Configuration**: In ORM libraries (like SQLAlchemy or SQLModel) or API frameworks, it is common to define database tables or routes declaratively. This pattern allows subclasses to configure themselves during creation.

***

## 4. Encapsulation

Encapsulation restricts direct access to an object's internal state to prevent accidental modifications.

### Private & Protected Fields

* **Protected** (`_name`): A convention suggesting the field is for internal/subclass use. Python does not enforce this.
* **Private** (`__pin`): Triggers Name Mangling (`_ClassName__pin`), causing Python to raise an `AttributeError` if accessed directly.

```python theme={null}
class User:
    def __init__(self, username: str, pin: str):
        self.username = username    # Public
        self._status = "active"     # Protected
        self.__pin = pin            # Private
```

## 5. Properties

### Property Getters & Setters (`@property`)

Properties allow you to define methods that behave like attributes, which is useful for validating data when reading or writing a field.

```python theme={null}
class Product:
    def __init__(self, name: str, price: float):
        self.name = name
        self._price = price

    @property
    def price(self) -> float:
        """Getter method"""
        return self._price

    @price.setter
    def price(self, value: float):
        """Setter method with validation"""
        if value < 0:
            raise ValueError("Price cannot be negative")
        self._price = value

# Usage
item = Product("Laptop", 999.0)
print(item.price)  # Accesses property getter -> 999.0
item.price = 1050.0  # Accesses property setter
```

***

## 6.Abstract Classes

Python provides built-in mechanisms to inspect objects at runtime, define interfaces/abstract classes, and customize how arguments are passed during class inheritance.

### Abstract Base Classes (ABCs)

The `abc` module allows you to define abstract classes that cannot be instantiated and force subclasses to implement specific methods.

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

class BaseRepository(ABC):
    @abstractmethod
    def save(self, data: dict) -> None:
        """Subclasses must implement this method"""
        pass

# repo = BaseRepository()  # TypeError: Can't instantiate abstract class BaseRepository

class SQLRepository(BaseRepository):
    def save(self, data: dict) -> None:
        print(f"Saving {data} to Database")
```

## 7. Introspection

### Introspection: `__dict__` and `__annotations__`

* **`__dict__`**: A dictionary containing the namespaces of an object (its attributes and values).
* **`__annotations__`**: A dictionary containing type annotations defined on the class or function.

```python theme={null}
class User:
    username: str
    email: str

    def __init__(self, username: str, email: str):
        self.username = username
        self.email = email

# Inspecting class annotations
print(User.__annotations__) 
# Output: {'username': <class 'str'>, 'email': <class 'str'>}

# Inspecting instance attributes
u = User(username="alice", email="alice@example.com")
print(u.__dict__) 
# Output: {'username': 'alice', 'email': 'alice@example.com'}
```

***

## Practice & Exercises

To reinforce what you've learned in this section (classes/instances, static/class methods, inheritance/**init\_subclass**, encapsulation, properties, abstract base classes, and introspection), practice with these interactive notebooks:

<CardGroup cols={2}>
  <Card title="Follow-Along Practice" icon="laptop-code">
    Practice class instantiation, defining instance/class/static methods, class inheritance, custom **init\_subclass** hooks, protected/private encapsulation, properties, ABC templates, and instance/class introspection.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/fastapi-course/public/notebooks/basics_exercises/OOP_Classes_Practice.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/fastapi-course/blob/master/public/notebooks/basics_exercises/OOP_Classes_Practice.ipynb) | <a href="/public/notebooks/basics_exercises/OOP_Classes_Practice.ipynb" download>📥 Download</a>
  </Card>

  <Card title="Practice Exercises" icon="pen-to-square">
    Test your knowledge with hands-on exercises including car factories, string parsers, department managers, secure bank accounts, notifications dispatchers, and class metadata scanners.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/fastapi-course/public/notebooks/basics_exercises/OOP_Classes_Exercises.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/fastapi-course/blob/master/public/notebooks/basics_exercises/OOP_Classes_Exercises.ipynb) | <a href="/public/notebooks/basics_exercises/OOP_Classes_Exercises.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>
