# Python's Object Model: References, Mutability and Copies — Data Structures in Python

Source: https://www.skillbyai.com/en/data-structures-python/f-model

> Understand names, references, mutability, identity and copying.

## Names point to objects

In Python, **variables are names bound to objects**; assignment never copies data, it makes another name refer to the same object. Every object has an **identity** (`id(x)`, compared with `is`), a **type** and a **value** (compared with `==`). Objects are **mutable** (`list`, `dict`, `set`, `bytearray`, most user classes) or **immutable** (`int`, `float`, `str`, `tuple`, `frozenset`, `bytes`). Mutability matters for data structures: two names for one list see each other's changes (**aliasing**); a mutable **default argument** (`def f(items=[])`) is created once and shared between calls; and only **hashable** objects (generally immutable ones) can be dictionary keys or set members. Containers hold **references**, so `[[0] * 3] * 3` creates three references to the **same** inner list. **Copying**: `list(x)`, `x[:]`, `x.copy()` and `copy.copy` make **shallow** copies (new container, same element objects), while `copy.deepcopy` recursively copies nested mutable objects. Small integers and some strings may be cached by CPython, which is why `is` must never be used to compare values; use `is` only for `None` and sentinel objects.

## Aliasing, shallow and deep copies

Common surprises and how to avoid them.

```python
import copy

cart = ["pen", "ink"]
same_cart = cart                 # another name, same list
same_cart.append("pad")
print(cart)                      # ['pen', 'ink', 'pad']
print(cart is same_cart)         # True

grid_bad = [[0] * 3] * 3         # three references to ONE row
grid_bad[0][0] = 1
print(grid_bad)                  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

grid = [[0] * 3 for _ in range(3)]   # three distinct rows
grid[0][0] = 1
print(grid)                      # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

orders = {"asha": ["o1", "o2"]}
shallow = copy.copy(orders)
deep = copy.deepcopy(orders)
orders["asha"].append("o3")
print(shallow["asha"])           # ['o1', 'o2', 'o3']: inner list is shared
print(deep["asha"])              # ['o1', 'o2']: fully independent

def add_item(item, items=None):  # never use a mutable default like items=[]
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("pen"), add_item("ink"))   # ['pen'] ['ink']
```

## Labels on boxes

A Python variable is a luggage label tied to a box, not the box itself. Tying a second label to the same box does not create a second box; whatever you put in it is visible through both labels.

**Quiz:** Why does `[[0] * 3] * 3` behave unexpectedly when you modify one row?

- [ ] Lists cannot be nested
- [ ] Integers are mutable
- [ ] It creates a tuple
- [x] The outer list holds three references to the same inner list

*Answer:* The outer list holds three references to the same inner list. Multiplying a list repeats references, not copies.
