Views, copies and shared memory
Slicing doesn’t copy — and that will edit your data behind your back exactly once.
.base.copy()np.shares_memory.flags.owndataview vs copyWatch it happen
Play it through, or step back and forth yourself.
ab = aStart with the simplest case. b = a doesn't copy anything — it's a second name for the same array. Everyone expects that one.
The idea
Slicing a Python list gives you a new list. Slicing a NumPy array gives you a view: a different window onto the same bytes. Nothing is copied, and writing through the window edits the original.
window = a[1:3, 1:3]
window[:] = 99
a # a has changed tooThis is deliberate, and it's the reason lesson 4 spent so long on strides: a view is just a new offset and a new set of strides pointing into memory somebody else owns. Slicing a two-gigabyte array costs nothing because nothing moves.
It's also a genuinely nasty class of bug, because the damage lands somewhere you weren't looking. You slice out a training set, normalise it, and discover much later that you normalised the original too.

Which operations copy?
The rule is mechanical once you think in strides:
# views — describable with an offset and strides
a[1:3] a[:, ::2] a.T
a.reshape(2, 6) a.ravel() a[np.newaxis]
# copies — the elements are scattered, or the type changed
a[[0, 2]] a[a > 5] a.flatten()
a.astype(float) a + 0 np.sort(a)Basic slicing is a view. Fancy indexing and boolean masks are copies, because you can't describe "rows 0 and 2" with a single stride. Anything that produces new values — arithmetic, a dtype change — obviously needs new memory.
ravel is the interesting one: it returns a view when the array is already contiguous and quietly copies when it isn't. flatten always copies, so use it when you want the guarantee.
How to check
np.shares_memory(a, b) # exact answer; can be slow on huge arrays
np.may_share_memory(a, b) # fast, errs on the side of "maybe"
b.base is None # True means b owns its data
b.flags.owndata # the same question, phrased positivelyA wrinkle about .base. It points at the ultimate owner of the memory, not necessarily the array you sliced. Because a = np.arange(12).reshape(3, 4) is itself a view of the array arange produced, this happens:
w = a[1:3, 1:3]
w.base is a # False — a doesn't own the memory either
w.base is a.base # True — both point at the original block
np.shares_memory(a, w) # True — the question you actually meantSo use .base is None as a quick "do I own this?" check, and np.shares_memory when you need to know whether two specific arrays overlap.

Working safely
Two habits cover almost everything. Copy when you intend to modify — .copy() is cheap to type and the bug it prevents is expensive. And when a function takes an array and modifies it, say so in the name, because the caller has no way to tell from the outside.
def normalise(x): # returns something new — safe
return (x - x.mean()) / x.std()
def normalise_inplace(x): # edits the caller's array — say so
x -= x.mean()
x /= x.std()Note x -= … versus x = x - …: the first writes into the existing memory (and therefore into every view of it), the second rebinds the name to a brand-new array and leaves the original alone. Same arithmetic, completely different consequences.
Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedTake window = a[1:3, 1:3] and print whether it shares memory with a. Set the whole window to 99, then print a — it changed underneath you. Now build a fresh (3, 4), take the same block with .copy(), set that to 99, and print again to show it didn't.
Your turn
3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Does a[0:2] share memory with a? Return the boolean.
Now the same question for a[[0, 2]] — fancy indexing rather than a slice. Return the boolean.
Set the first row of a copy of a to zero, then return a — which must come back unchanged. The starter gets it wrong.
