> ## 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.

# Python Internals: Memory Model, Objects & References

> Understand Python's memory model — mutable vs immutable types, object identity, integer caching, reference counting, and garbage collection.

Writing efficient and bug-free Python code becomes much easier once you understand what happens under the hood. Every value you create lives as an object in memory, variables are pointers to those objects rather than containers for raw data, and Python manages memory automatically through reference counting and garbage collection. This page demystifies Python's memory model so you can reason confidently about object identity, mutable vs. immutable types, and why `is` and `==` sometimes give surprising results.

## Mutability vs. Immutability

Every variable in Python is a **reference** pointing to an object in memory. Objects fall into two categories:

### Mutable Objects

A **mutable** object can be changed after it is created. Its memory address stays the same even after modification:

```python theme={null}
numbers = [1, 2, 3]
print(id(numbers))     # e.g. 4398114048

numbers.append(4)
print(numbers)         # [1, 2, 3, 4]
print(id(numbers))     # 4398114048  — same address!
```

Common mutable types: **`list`**, **`dict`**, **`set`**

### Immutable Objects

An **immutable** object cannot be changed. Any "modification" creates a **new** object at a new memory address:

```python theme={null}
name = "Alice"
print(id(name))        # e.g. 4398201904

name += " Smith"
print(id(name))        # e.g. 4398205424  — different address!
```

Common immutable types: **`int`**, **`float`**, **`str`**, **`tuple`**, **`bool`**

<Warning>
  Tuples are immutable, but if a tuple **contains** a mutable object like a list, the list's contents can still be modified:

  ```python theme={null}
  my_tuple = (1, [2, 3])
  my_tuple[1].append(4)
  print(my_tuple)   # (1, [2, 3, 4])  — the list changed!
  ```

  The tuple itself didn't change (it still references the same list), but the list's contents did.
</Warning>

## Everything is an Object

In Python, **every value is a first-class object**, including integers, strings, functions, modules, and classes. Each object carries three things:

1. **A Value** — the data itself.
2. **A Type** — defines what the object can do.
3. **An Identity** — a unique integer from `id()` representing the memory address.

```python theme={null}
def greet():
    return "Hello!"

# Functions are objects too
func_reference = greet
print(func_reference())   # Hello!
print(type(greet))        # <class 'function'>
```

## Identity (`id()`) vs. Equality (`==`)

This distinction is one of the most important in Python:

* **`==`** (equality): compares **values** — calls `__eq__` internally.
* **`is`** (identity): compares **memory addresses** — checks if two variables point to the exact same object.

```python theme={null}
list_a = [1, 2, 3]
list_b = [1, 2, 3]

print(list_a == list_b)          # True  — same value
print(list_a is list_b)          # False — different objects in memory
print(id(list_a) == id(list_b))  # False
```

<Tip>
  **Integer Caching:** CPython caches integers from `-5` to `256` for performance. This means small integers with the same value share the same object:

  ```python theme={null}
  x = 100
  y = 100
  print(x is y)   # True  — cached, same object

  a = 300
  b = 300
  print(a is b)   # False — not cached, different objects
  ```

  Never rely on `is` for integer comparisons. Use `==` instead.
</Tip>

## Reference Counting

Python manages memory automatically. Its primary mechanism is **reference counting**:

* Every object tracks how many variables reference it.
* When you assign an object to a variable, its reference count increases by 1.
* When a variable goes out of scope or is reassigned, the count decreases by 1.
* When the count reaches **0**, Python destroys the object and frees its memory.

```python theme={null}
import sys

a = [1, 2, 3]          # ref count = 1
b = a                  # ref count = 2

# sys.getrefcount() temporarily adds 1 during the call
print(sys.getrefcount(a))   # 3
```

<Warning>
  **Circular references** — where Object A references Object B and Object B references Object A — can prevent reference counts from ever reaching zero. Python detects and cleans these up using a secondary **Generational Garbage Collector**. You can trigger it manually with `gc.collect()` from the `gc` module.
</Warning>

## Summary

| Concept            | Key Point                                                  |
| ------------------ | ---------------------------------------------------------- |
| Mutable types      | Changed in-place; `id()` stays the same                    |
| Immutable types    | Modification creates a new object at a new address         |
| `==`               | Compares values                                            |
| `is`               | Compares memory addresses (object identity)                |
| Integer caching    | `-5` to `256` reuse the same object in CPython             |
| Reference counting | Object is freed when its reference count hits 0            |
| Garbage collector  | Handles circular references that defeat reference counting |
