The ndarray
What it is
A fixed-size, n-dimensional grid of values that all share one dtype, stored as a single contiguous block of memory with a shape describing how to read it.
Why it matters
Because the values are unboxed and adjacent, a whole array can be processed in compiled code with no Python object per element. That is the entire performance story, and it is also why an array cannot grow in place the way a list can.
In the real world: A year of hourly electricity readings for 1,000 homes is one array: fast to average, compare and plot, unlike a list of lists.
Visual example
NumPy ndarrayAn array is one block of memory plus a shape: no per-row labels, no per-column dtypes, just positions.
The same array, described
arr.shape → (2, 3)
arr.dtype → int64
arr.size → 6
arr.nbytes → 48
Why this matters
Shape, dtype and contiguity are the three things that decide whether an operation is legal, exact, and fast. Reading an array as a picture of those three is what makes NumPy predictable.
Common misconception
Wrong idea: an ndarray is a list of lists.
Correct idea: it is a flat buffer of same-dtype values with a shape laid over it. arr[1, 2] is arithmetic on one index, not two lookups.
- 1One bufferSix int64 values, adjacent in memory.
- 2One shape
(2, 3)says how to fold that buffer into rows and columns. - 3One index per axis
arr[1, 2]reads position 1 * 3 + 2 of the buffer.
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr.shape # (2, 3)
arr.dtype # dtype('int64')
arr[1, 2] # 6 — one index per axis, not arr[1][2]Treating an array like a list: arr.append(x) does not exist, and np.append(arr, x) allocates a whole new array every call. Building an array by repeated appending in a loop is quadratic. Collect into a Python list and call np.array once, or preallocate with np.empty and fill by index.