Kiru Lab / Foundations: Python as an Instrument / Python From First Principles
Values, Names, and Objects
Assignment does not copy. Understanding that one fact prevents a whole class of bugs later.
Concept · about 25 minutes
A Python program is a set of objects living in memory and a set of names pointing at them. The assignment operator does not create a copy of anything; it binds a name to an object that already exists. Every confusing bug you will hit in the next six months about "why did my array change" comes back to this sentence.
a = [1, 2, 3]
b = a # b is not a copy — it is a second label on the same list
b.append(4)
print(a) # [1, 2, 3, 4]
c = a[:] # now this is a copy
c.append(5)
print(a) # [1, 2, 3, 4] — unchangedMutable and immutable
Some objects can be changed in place (lists, dicts, sets, NumPy arrays, and most model objects you will meet). Others cannot (integers, floats, strings, tuples). When you "change" an immutable object you are really making a new one and re-pointing the name. This distinction decides whether a function can quietly modify its caller's data.
def add_bias(weights, bias):
weights.append(bias) # mutates the caller's list
return weights
w = [0.1, 0.2]
add_bias(w, 1.0)
print(w) # [0.1, 0.2, 1.0] — the caller's data changed
def add_bias_safely(weights, bias):
return weights + [bias] # builds a new list, leaves the input aloneInterpretability work is full of hooks that capture activations mid-forward-pass. A hook that stores a reference instead of a copy will show you a tensor that has already been overwritten by the next layer. The bug is not in PyTorch; it is this lesson.
Types are contracts, not decoration
Python does not force you to declare types, but the objects still have them, and a type is a promise about what operations are available. Getting into the habit of writing type hints costs seconds and saves hours once your code has more than one author.
def mean(values: list[float]) -> float:
"""Arithmetic mean. Raises ZeroDivisionError on an empty list."""
return sum(values) / len(values)Hold on to
- Assignment binds a name to an object; it never copies
- Mutable objects can be changed through any name pointing at them
- A function that mutates its arguments has a hidden output
Work through
Try each one before opening the solution. Getting it wrong first is most of where the learning happens.
-
Write a function that appears to reverse a list but leaves the caller's list untouched. Then write the version that does mutate it. Explain in one sentence when each is the right choice.
Hint
One version builds a new list; the other calls a method that changes the original.
Solution
Use slicing or `reversed()` to build a new list; use `.reverse()` to mutate in place. Prefer the non-mutating version by default — a function that changes its caller's data has an invisible second output. Mutate only when the data is large enough that copying is a real cost, and then say so in the function name.
def reversed_copy(items): return items[::-1] # new list, caller untouched def reverse_in_place(items): items.reverse() # mutates; returns None on purpose return NoneCheck your work
Paste this after your own code. If it runs without raising, you have it.
original = [1, 2, 3] assert reversed_copy(original) == [3, 2, 1] assert original == [1, 2, 3] # unchanged reverse_in_place(original) assert original == [3, 2, 1] # changed print("ok") -
Predict the output of a nested-list copy (`b = a[:]` where `a = [[1], [2]]`, then `b[0].append(9)`) before running it. Explain the result.
Hint
A slice copies the outer list. What does it copy the inner lists into?
Solution
`b` prints as `[[1, 9], [2]]` — the copy is shallow. `b = a[:]` made a new outer list, but its elements are the same inner list objects, so mutating through either name is visible through both. Use `copy.deepcopy` when you need independence all the way down.
import copy a = [[1], [2]] b = a[:] # shallow — inner lists shared b[0].append(9) print(a) # [[1, 9], [2]] c = copy.deepcopy(a) # independent all the way down c[0].append(99) print(a) # [[1, 9], [2]] — unchanged
In the Bio Mirror
The SynapseFurther reading
- Python docs — Data model
- Ned Batchelder, "Facts and Myths about Python Names and Values"
Sign in to track your progress through the lab.