Copy vs View
Know exactly when two arrays share data — assignment, views (slices, reshape, .T) and copies (.copy(), fancy indexing) — and check it with np.shares_memory and .base.
You take the first 3 matches of Virat's season with a slice, set a score to 0 for a "what-if" experiment… and later find Virat's original data has changed too. This is the most common surprise in NumPy. It happens because many operations don't create new data — they give you a view of the same memory. Knowing when you get a view and when you get a copy is essential for correct analysis.
Definition
A view is a new array object that shares the same data buffer (memory) as another array — it is just a different "window" onto the same numbers. A copy is a new array with its own separate data. Changing a view changes the original; changing a copy never does.
There are actually three situations:
| Situation | Code | New array object? | Shares data? | Edit affects original? |
|---|---|---|---|---|
| No copy (assignment) | b = a | ❌ same object (b is a) | ✅ | ✅ yes |
| View | b = a[1:4], a.reshape(..), a.T, a.ravel(), a.view() | ✅ | ✅ | ✅ yes |
| Copy | b = a.copy(), a[[0, 2]], a[a > 50], a.flatten(), a + 0 | ✅ | ❌ | ❌ no |
- Data buffer — the block of memory where the actual numbers live. The array object only stores shape, dtype and "strides" (how to walk through that memory).
- Why views exist — speed and memory. Slicing a 1 GB array is instant because nothing is copied.
Syntax — the tools
python
b = a.copy() # independent copy (also np.copy(a))
v = a.view() # explicit view
np.shares_memory(a, b) # True if a and b use overlapping memory
b.base # the array that owns b's memory (None if b owns its data)
b.flags["OWNDATA"] # True if b owns its data| Tool | Returns | Meaning |
|---|---|---|
a.copy(order="C") | new array | deep copy of the data (order = memory layout, default "C") |
np.shares_memory(a, b) | bool | exact check whether the two arrays overlap in memory |
np.may_share_memory(a, b) | bool | fast, approximate check (can give false positives) |
x.base | array or None | for a view: the array that owns the memory; for an owner: None |
Example — "Important: .copy() matters" (from the course notebook)
When you write new_array = old_array, you do not get a new array — you get another name pointing to the same data.
python
import numpy as np
old_scores = np.array([72, 45, 88, 31, 64])
new_scores = old_scores # just a second name!
new_scores[0] = 0
print("old after editing new:", old_scores)
print("same object?", new_scores is old_scores)
old_scores = np.array([72, 45, 88, 31, 64])
safe = old_scores.copy() # a real, independent array
safe[0] = 0
print("old after editing copy:", old_scores)
print("copy:", safe)text
old after editing new: [ 0 45 88 31 64]
same object? True
old after editing copy: [72 45 88 31 64]
copy: [ 0 45 88 31 64]How it works: = never copies anything in Python — it just attaches another name to the same object. .copy() allocates new memory and copies the values into it, so the experiment is isolated. Always copy before modifying data you want to keep — which during analysis is almost always.
Example — slices are views
python
import numpy as np
virat = np.array([72, 45, 88, 31, 64, 0, 103, 52])
first3 = virat[:3] # basic slice -> VIEW
first3[0] = 999
print("virat :", virat)
print("first3.base is virat:", first3.base is virat)
print("shares memory:", np.shares_memory(virat, first3))
virat = np.array([72, 45, 88, 31, 64, 0, 103, 52])
first3 = virat[:3].copy() # explicit copy
first3[0] = 999
print("virat :", virat)
print("base:", first3.base, " shares memory:", np.shares_memory(virat, first3))text
virat : [999 45 88 31 64 0 103 52]
first3.base is virat: True
shares memory: True
virat : [ 72 45 88 31 64 0 103 52]
base: None shares memory: FalseHow it works: a basic slice (start:stop:step) can always be described as "the same memory, different start/size/stride", so NumPy returns a view. After .copy() the slice has its own memory and .base is None.
Note —
.basepoints to the memory owner: if you take a view of a view,.baseskips the middle step and points straight at the array that actually owns the data. So prefernp.shares_memorywhen you just want a yes/no answer.
python
import numpy as np
a = np.arange(6) # a owns its data
b = a.reshape(2, 3) # view of a
c = b.T # view of the view
print("a.base:", a.base)
print("b.base is a:", b.base is a)
print("c.base is a:", c.base is a, " c.base is b:", c.base is b)
print("shares memory with a:", np.shares_memory(a, c))text
a.base: None
b.base is a: True
c.base is a: True c.base is b: False
shares memory with a: TrueExample — what gives a view, what gives a copy
python
import numpy as np
a = np.arange(12).reshape(3, 4)
tests = {
"a[1] (row)": a[1],
"a[:, 1:3] (slice)": a[:, 1:3],
"a[::2] (step slice)": a[::2],
"a.T (transpose)": a.T,
"a.reshape(4,3)": a.reshape(4, 3),
"a.ravel()": a.ravel(),
"a.flatten()": a.flatten(),
"a[[0, 2]] (fancy)": a[[0, 2]],
"a[a > 5] (boolean)": a[a > 5],
"a + 0 (math)": a + 0,
"a.copy()": a.copy(),
}
for name, arr in tests.items():
kind = "VIEW" if np.shares_memory(a, arr) else "copy"
print(f"{name:<28} {kind}")text
a[1] (row) VIEW
a[:, 1:3] (slice) VIEW
a[::2] (step slice) VIEW
a.T (transpose) VIEW
a.reshape(4,3) VIEW
a.ravel() VIEW
a.flatten() copy
a[[0, 2]] (fancy) copy
a[a > 5] (boolean) copy
a + 0 (math) copy
a.copy() copyHow it works: basic indexing (integers and slices), reshape, .T and ravel describe the same memory differently → views. Advanced indexing (a list/array of indices, or a boolean mask) can pick arbitrary elements in any order, which can't be described as a simple stride pattern → always a copy. Arithmetic always creates new results.
Example — the boolean-mask trap (and why it isn't one for assignment)
python
import numpy as np
scores = np.array([72, 0, 88, 0, 64])
ducks = scores[scores == 0] # a COPY of the matching values
ducks[:] = -1
print("after editing the copy:", scores)
scores[scores == 0] = -1 # assignment THROUGH the mask modifies in place
print("after masked assignment:", scores)text
after editing the copy: [72 0 88 0 64]
after masked assignment: [72 -1 88 -1 64]How it works: scores[mask] on the right side of = creates a copy. But scores[mask] = value on the left side is a special "set items" operation that writes directly into scores. The same is true for fancy indexing.
Comparison — view vs copy
| View | Copy | |
|---|---|---|
| Memory | shares the original's buffer | owns a new buffer |
| Creation cost | almost free (no data copied) | proportional to size |
| Editing it | changes the original | original is safe |
.base | the array that owns the memory | None |
np.shares_memory(orig, x) | True | False |
| Made by | basic slicing, reshape, .T, ravel, .view(), newaxis, squeeze | .copy(), np.copy, fancy/boolean indexing, flatten, arithmetic, np.array(a) |
Common mistakes
- ❌
backup = scoresand believing you have a backup. ✅backup = scores.copy(). - ❌ Taking a slice, modifying it, and corrupting the original data. ✅
part = scores[:5].copy()if you will modifypart. - ❌ Assuming
a[[0, 1]]is a view becausea[0:2]is. ✅ Fancy and boolean indexing always copy. - ❌ Checking with
==(np.array_equal) to see if two arrays are "the same". ✅ Equal values ≠ shared memory; usenp.shares_memoryor.base. - ❌ Keeping a tiny slice of a huge array alive — the view keeps the whole big buffer in memory. ✅
small = big[:10].copy()and letbiggo.
Interview tip
Q: What is the difference between b = a, b = a.view() and b = a.copy()?
A: b = a creates no new object — b is a is True. a.view() creates a new array object that shares a's data, so changing values in b changes a (but changing b's shape does not reshape a). a.copy() creates a new object with its own data; nothing done to b affects a.
Key points to remember
- b = a makes no copy at all — it's a second name for the same object (b is a).
- A view is a new array object that shares the original's memory; editing it edits the original.
- Basic slices, reshape, .T, ravel, .view(), newaxis and squeeze return views.
- A copy owns separate memory: .copy(), np.copy, fancy indexing a[[0, 2]], boolean masks a[a > 5], flatten and arithmetic.
- Check with np.shares_memory(a, b) or b.base (None means b owns its data).
- a[mask] = value writes into a in place, even though a[mask] used as a value is a copy.
- Always .copy() before modifying a version of data you want to keep untouched.
Try it yourself
Assignment vs .copy()
Slices are views
View or copy? Test it yourself
Practice: a safe what-if experiment
Revision Flashcards
Tap a card to flip it and see the answer.
