What does memoryview.cast() change about a view, and why can a view's len differ from its nbytes?
answer
- The bytes do not move
- Only the grid over them changes
- Elements versus bytes are different counts
- itemsize is the bridge between the two
- Route non-byte formats through 'B'
basics
~10 smemoryview.cast reinterprets the same memory under a different element format without copying, so a 4-element unsigned-int view becomes a 16-element byte view. len counts elements, nbytes counts bytes, so they differ for wider elements.
solid answer
~40 s`cast()` changes how a view *interprets* bytes, never the bytes themselves: it returns a new `memoryview` over the same storage with a different format string and shape. A view over `array.array('I', [1, 2, 3, 4])` reports `format` `'I'`, `itemsize` 4, `len` 4 and `nbytes` 16; `cast('B')` gives `format` `'B'`, `itemsize` 1, `len` 16 and the same `nbytes` 16. That is the answer to the second half: `nbytes` is the product of the shape times `itemsize`, while `len` is the first dimension, so the two coincide only for byte-sized elements. Two restrictions matter — casts are restricted to C-contiguous views, so a stepped slice is refused, and you cannot cast directly between two non-byte formats; go through `'B'`. Endianness and signedness are not converted, they are reinterpreted.
code
python · 9 linesimport array
counts = array.array("I", [1, 2, 3, 4])
mv = memoryview(counts)
print(mv.format, mv.itemsize, len(mv), mv.nbytes) # I 4 4 16
raw = mv.cast("B")
print(raw.format, raw.itemsize, len(raw), raw.nbytes) # B 1 16 16
print(raw.tobytes() == counts.tobytes()) # Truego deeper
Recall that a memoryview reports both a length in elements and an nbytes in bytes, and that they only coincide for byte-sized elements. You are not expected to reach for cast at this level.
Explain that cast reinterprets the same storage under a new format and shape, that nbytes equals the shape product times itemsize, and that casts need a C-contiguous view and route non-byte formats through 'B'.
Show where it pays off: reading fixed-width fields out of a block already in memory without building intermediate lists, and knowing cast performs no endianness conversion so struct is the right tool when byte order is defined by a wire format.
Judge whether reinterpretation belongs in your codebase at all — a cast is an untyped reinterpretation with platform-dependent results, and a documented struct-based parse is often the more maintainable contract even when it copies.
## Reinterpretation, not conversion A `memoryview` carries a description of memory: a `format` string in `struct` syntax, an `itemsize`, a `shape` tuple, `strides`, and a read-only flag. `cast()` produces a new view over the *same bytes* with a different format and shape. Nothing is converted, rounded, byte-swapped or copied — the same memory is simply read through a different lens. ```python import array counts = array.array("I", [1, 2, 3, 4]) mv = memoryview(counts) print(mv.format, mv.itemsize, len(mv), mv.nbytes) # I 4 4 16 raw = mv.cast("B") print(raw.format, raw.itemsize, len(raw), raw.nbytes) # B 1 16 16 print(raw.tobytes() == counts.tobytes()) # True ``` Sixteen bytes, seen as four unsigned ints or as sixteen unsigned bytes. `nbytes` is invariant across the cast because the memory is invariant; only the element grid over it moved. ## Why len and nbytes are different questions `len(view)` is the size of the first dimension — the number of *elements* along it. `view.nbytes` is the total number of *bytes* described, equal to the product of `shape` times `itemsize`. For a plain view over `bytes` or `bytearray` the format is `'B'` and `itemsize` is 1, so the two numbers coincide, and that coincidence is exactly why the distinction surprises people the first time they take a view over an `array.array` of a wider type. The practical consequence: size a destination buffer from `nbytes`, and drive a loop from `len()`. Confusing them under a 4-byte format is a silent factor-of-four bug — the loop reads a quarter of the data, or the buffer is a quarter of the size it needed to be. `itemsize` is the bridge between them, and it is also platform-dependent for some `array` typecodes, which is a reason to read `itemsize` rather than assume it. ## The two restrictions on cast **Casts are restricted to C-contiguous views.** A view whose elements are not laid out back-to-back cannot be regridded, because there is no single element size that describes the layout: ```python mv = memoryview(bytearray(b"abcdefgh")) stepped = mv[::2] print(bytes(stepped), stepped.c_contiguous) # b'aceg' False stepped.cast("H") # TypeError: memoryview: casts are restricted to C-contiguous views ``` Check `c_contiguous` before casting anything that came out of a stepped slice. **You cannot cast between two non-byte formats directly.** Going from `'I'` to `'H'` raises `TypeError: memoryview: cannot cast between two non-byte formats`. The byte format is the hub: `mv.cast('B').cast('H')` is the supported route, and it makes the reinterpretation explicit — you are flattening to bytes and then regridding them. ## Reshaping as well as reformatting `cast()` takes an optional shape, so it also flattens and un-flattens: ```python flat = memoryview(bytearray(16)) grid = flat.cast("B", shape=(4, 4)) print(grid.ndim, grid.shape, len(grid), grid.nbytes) # 2 (4, 4) 4 16 grid[1, 2] = 9 print(flat[6]) # 9 - same memory ``` The product of the requested shape times `itemsize` must equal `nbytes`; anything else is a `TypeError`. Note the indexing form: a multi-dimensional view is indexed with a tuple, `grid[1, 2]`, because chained `grid[1][2]` raises `NotImplementedError: multi-dimensional sub-views are not implemented`. Two-dimensional views are also where the difference between `len` (4, the first dimension) and `nbytes` (16) becomes unmissable. ## What cast does not do It does not convert endianness. Reading four little-endian bytes as an `'I'` on a big-endian machine gives you a different number, and `cast()` will not warn you — the `format` codes it accepts are native formats, so byte order follows the platform. If you need a defined byte order, that is `struct` territory, not `cast()` territory. It does not check alignment for you in any meaningful sense either: reinterpreting a byte view as a wider type starting at an arbitrary offset is a shape error if the length does not divide evenly, but the semantics of misaligned data are yours to reason about. And it does not copy. The result shares storage with the original, so a write through `raw` in the first example is visible through `mv`, and both keep the `array.array` pinned and unresizable until released. ## Where it earns its keep The common use is at a boundary between a byte-oriented API and a typed one: you read a block of bytes from a stream into a `bytearray`, and you want to look at it as fixed-width integers without building an intermediate list. `memoryview(buf).cast('I')` gives you indexable integers over the bytes you already have, in constant extra memory. The reverse — `memoryview(typed_storage).cast('B')` — is how you hand typed storage to something that only speaks bytes. Both directions are free; both keep the exporter pinned. The short version: `cast()` changes the lens, not the picture; `nbytes` is the picture and `len` is the lens's first dimension; and the two restrictions to remember are C-contiguity and the byte-format hub.
- Why does casting a view of array.array('I', ...) straight to 'H' fail?`cast()` refuses to go between two non-byte formats: it raises `TypeError: memoryview: cannot cast between two non-byte formats`. The byte format is the hub, so the supported route is `mv.cast('B').cast('H')`. The restriction is deliberate — it forces you to say out loud that you are flattening to raw bytes and then regridding them, rather than implying a numeric conversion that never happens.
- Does cast() ever change the numbers you read back?Only in the sense that a different lens reads different numbers from the same bytes. It performs no conversion: no widening, no rounding, no byte swapping. Because the accepted format codes are native, byte order follows the platform, so reinterpreting bytes as a wider integer type gives a platform-dependent value. If byte order must be defined, use `struct.unpack_from` on the view instead.
- When would len(view) and view.nbytes give the same number?Whenever `itemsize` is 1 and the view is one-dimensional — that is, a plain view over `bytes`, `bytearray` or anything cast to `'B'`. Because that is the most common case, people generalise from it and are then caught by a 4-byte format, where `len` is a quarter of `nbytes`. Size destination buffers from `nbytes` and drive loops from `len()`.
Casting is swapping the ruler, not the plank: the same length of wood measured in inches or centimetres gives different counts and the same amount of wood.
saying these in an interview costs you the question
- Thinks cast converts or widens the values numerically
- Assumes cast copies the data into a new buffer
- Believes len and nbytes are always equal
- Tries to cast a stepped, non-contiguous slice
- Expects a direct cast between two wide formats
- Assumes cast normalises endianness for you