What an ndarray actually is
One type, one block of memory — and why everything else follows from that.
np.array.shape.ndim.dtype.itemsize.nbytesWatch it happen
Play it through, or step back and forth yourself.
aa.shape(3, 4)size along each axisa.ndim2how many axesa.dtypeint64one type, whole arraya.itemsize8 bytesevery element, identicala.nbytes96 bytes12 x 8, contiguous
This is an ndarray: a grid of numbers, all the same type, laid out in one unbroken block of memory. It looks like a list of lists. It is nothing like a list of lists.
The idea
A NumPy array looks like a list of lists, and that resemblance is the source of most early confusion. They are built on completely different ideas.
A Python list is a row of pointers. Each one points off to a separate object living somewhere else in memory, and those objects can be anything — an int, a string, another list. Flexible, and slow: reading ten million values means following ten million pointers.
An ndarray is one unbroken block of memory holding values of a single type. No pointers, no per-element objects. Element [2][1] isn't looked up, it's calculated — start address, plus offset, done.

Shape and axes
a.shape is a tuple with one entry per axis. For (3, 4): axis 0 has length 3, axis 1 has length 4. It's tempting to translate that to "3 rows, 4 columns", and for 2-D that's harmless — but the habit breaks the moment you meet a 3-D array, and every image you ever load will be 3-D. Learn to say "axis 0" now and save yourself the retraining.
a.ndim is just len(a.shape). a.size is the product — the total number of elements.

One dtype for the whole array
Every element shares one type, fixed when the array is created. This isn't a restriction bolted on, it's the thing that makes the contiguous block possible: uniform type means uniform size means calculable addresses.
The consequence catches people out. Put a float into an int array and NumPy doesn't widen that one element — it has nowhere to put it. Depending on how you do it, either the value is truncated, or the whole array is rebuilt as float:
a = np.array([1, 2, 3]) # dtype int64
a[0] = 9.7 # truncated to 9 — no warning
np.array([1, 2, 3.5]) # dtype float64 — the whole array
And mix in a string and everything becomes a string. If an array's dtype ever surprises you, check what you fed the constructor.
Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedPrint a, then its shape, ndim, size and itemsize — each with a label so you can tell them apart. Finish with a.dtype on a line of its own, so it comes back as the result.
Your turn
3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Return the shape of cups.
Build an array from [1, 2, 3] forced to float64, then return its dtype to prove it worked.
How many bytes does a take up in total? Return the number.
