Views, copies and memory layout
How NumPy shares memory between arrays, what C and Fortran order really mean, and how to stop copying gigabytes by accident.
Views, copies and .base
Two arrays can point at the same bytes. When they do, one is a view of the other: writing through the view changes the original, and nothing is copied. The .base attribute tells you whether an array owns its memory.
import numpy as np
a = np.arange(6).reshape(2, 3)
v = a[0] # a view onto row 0
v[0] = 99
a[0, 0] # 99 - the original changed
c = a[0].copy() # independent memory
c[0] = -1
a[0, 0] # still 99
v.base is a # True - v knows who owns the buffer
a.base # None - a owns its own data
np.shares_memory(a, v) # True- Slicing and
reshapegive views; boolean masks and fancy indexing always give copies. arr.baseis the fastest way to spot an accidental view in a debugger.np.shares_memory(x, y)answers the question directly for any pair of arrays.- A view keeps the whole original buffer alive, so a small slice of a huge array still costs the huge array.
C order, Fortran order and strides
Memory is one-dimensional; the array's shape is an interpretation layered on top of it. strides says how many bytes to step to move one position along each axis. That is why a transposed array is a view with different strides rather than rearranged data.
| Layout | Meaning | Where you meet it |
|---|---|---|
| C_CONTIGUOUS | Last axis varies fastest (row-major) | Default for most arrays |
| F_CONTIGUOUS | First axis varies fastest (column-major) | Images and Fortran/BLAS code |
| Non-contiguous | Strides larger than the element size | Transposes and column slices |
| Zero-stride | Several elements share one address | Broadcasting an axis of length 1 |
a = np.arange(6, dtype=np.int64).reshape(2, 3)
a.strides # (24, 8) - bytes per step along each axis
a.T.strides # (8, 24) - the transpose is just a view
a.T.flags["C_CONTIGUOUS"] # False
np.ascontiguousarray(a.T) # force a real copy into C order
np.asfortranarray(a) # column-major copy for BLAS-friendly code
a[:, 1].strides # (24,) - a column slice, not contiguousWhere the copies come from
m = np.ones((1000, 1000))
m.ravel() # view when the array is contiguous
m.flatten() # always a copy
m.T @ m # no copy: transposes feed BLAS directly
m.astype(np.float32) # always a copy: the dtype changes
m[m > 0.5] # copy: boolean mask output
np.asarray(m) is m # True - no copy when it is already an array
np.array(m, copy=False) # NumPy 2 raises if a copy would be required⚠️
Reshaping a non-contiguous array forces a hidden copy.
a.T.reshape(-1) allocates a full duplicate, and in a tight loop that is where memory blows up. Reshape, then transpose, if the layout matters.FAQ
How do I know if an operation copied?
Compare
arr.base and np.shares_memory on the input and output. If the output has no .base and does not share memory, it owns fresh data.Should I store my arrays in Fortran order?
Only when a downstream library (BLAS-heavy linear algebra, some image code) prefers it. Otherwise stick to C order and convert with
np.ascontiguousarray at the boundary.Related
Performance: einsum, ufunc tricks and avoiding copies Debugging array code: shape errors and NumPy 2 pitfalls
Last refreshed 2026-09-18.