Module 2 · Lesson 4
Views vs. Copies
This is the single most important mental model in this module:
| Operation | View or copy? |
|---|---|
Basic slicing (arr[1:4], grid[:, 0]) | View |
Boolean indexing (arr[mask]) | Copy |
Fancy indexing (arr[[0, 2]]) | Copy |
.reshape() (when possible) | View |
.ravel() | View when possible, else copy |
.flatten() | Always a copy |
A view shares the same underlying memory as the array it came from — arr2 = arr[1:4] doesn’t copy any data, it just creates a new array object that
looks at part of arr’s buffer. Mutating a view mutates the original.
Check arr2.base is arr (or arr2.base is not None) to tell if something
is a view — though .base chains can get indirect (a view of a view), so
np.shares_memory(a, b) is the more robust way to ask “do these two arrays
point at the same underlying memory?” directly, without worrying about how
many .base hops apart they are. Call .copy() whenever you need a truly
independent array.
original = np.array([1, 2, 3, 4, 5])
view = original[1:4]
print("view.base is original:", view.base is original)
view[0] = 999 # mutating the VIEW...
print("original:", original) # ...mutates the ORIGINAL too!
safe_copy = original[1:4].copy()
safe_copy[0] = -1
print("original after mutating the copy:", original) # unaffected
Output:
view.base is original: True
original: [ 1 999 3 4 5]
original after mutating the copy: [ 1 999 3 4 5]
A function that must not mutate its input
Write double_first_n(arr, n) that returns a new array equal to arr
but with its first n elements doubled. Calling this function must leave
the original arr completely unchanged — a common bug is slicing (which
gives a view) and then mutating in place.
💡 Hint
Start from arr.copy(), not from arr directly or a slice of it — then mutate the copy freely.
✅ Solution
def double_first_n(arr, n):
out = arr.copy()
out[:n] *= 2
return out
Is it a view?
Write is_view_of(sub, original) that returns True if sub is a view
into original's memory (i.e. shares its buffer), and False if sub is
an independent copy. Hint: this should work for both the array returned by
slicing AND the array itself (original.base is None, but it IS the data
— treat that case as "not a copy", i.e. return True only when sub is
genuinely backed by original's memory).
💡 Hint
np.shares_memory(sub, original) answers exactly this question and handles all the edge cases correctly — no need to reinvent it with .base.
✅ Solution
def is_view_of(sub, original):
return bool(np.shares_memory(sub, original))