pvdocs /Python/Pass by Reference/Value

Pass by Reference/Value

Python uses neither classic pass-by-value nor pass-by-reference. The correct term is pass by object reference (or "pass by assignment").

The Mental Model

Immutable Objects (int, str, tuple, float…)

The reference is copied. Rebinding inside the function has no effect outside.

def try_change(x):
    x = 100    # rebinds local x — original is untouched

n = 42
try_change(n)
print(n)  # still 42

Mutable Objects (list, dict, set, custom objects…)

The reference is copied, but both labels point to the same object. Mutations are visible outside.

def append_item(lst):
    lst.append(99)    # mutates the same object

numbers = [1, 2, 3]
append_item(numbers)
print(numbers)  # [1, 2, 3, 99]

But rebinding the name inside the function still has no effect:

def try_replace(lst):
    lst = [10, 20]   # just rebinds the local label

numbers = [1, 2, 3]
try_replace(numbers)
print(numbers)  # [1, 2, 3]  — unchanged

Visualizing with id()

def show(obj):
    print(id(obj))   # same address as the caller's object

x = [1, 2, 3]
print(id(x))  # e.g. 140234567
show(x)       # same value — same object

How to Avoid Unintended Mutation

# Pass a copy
append_item(numbers[:])         # shallow copy
append_item(list(numbers))      # shallow copy
append_item(copy.deepcopy(numbers))  # deep copy

# Or make the function document its mutating intent clearly

Mutable Default Argument Trap

A classic Python gotcha:

# BAD — the list is created once and shared across calls
def add(item, lst=[]):
    lst.append(item)
    return lst

add(1)  # [1]
add(2)  # [1, 2]  ← unexpected!

# GOOD
def add(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

Key Interview Points