What does memoryview give you that slicing a bytes object does not?
answer
- One of them moves no data
- A window onto someone else's memory
- Watch what len() counts after a cast
- tobytes copies, release ends the export
basics
~20 sSlicing bytes copies the sliced region into a new object; memoryview wraps the original memory, so slicing a view costs nothing but a small window object. A view of a bytearray can also write straight through to the source.
solid answer
~40 s`memoryview` exposes an object's memory through the buffer protocol without copying it. Slicing a view yields another view over the same bytes, so parsing a large frame costs no allocations, and assigning through a view of a `bytearray` mutates the original — views of `bytes` are read-only and raise `TypeError` on write. `cast()` reinterprets the region under a different item format, after which `len()` counts items rather than bytes and `nbytes` holds the byte count. `tobytes()` is the explicit copy back out. `release()`, or using the view as a context manager, ends the export — which matters because a `bytearray` cannot be resized while any view of it is alive; it raises `BufferError`.
code
python · 12 linesframe = bytearray(b"\x00\x01" * 8)
view = memoryview(frame)
header = view[:4] # a view, not a copy
header[0] = 0xFF # writes straight through to frame
print(frame[:4])
words = view.cast("H") # reinterpret as 2-byte unsigned items
print(len(view), len(words), words[0])
print(header.tobytes()) # an explicit copy
header.release()
words.release()
view.release()
frame += b"\x00" # only legal once no view is exportedgo deeper
Recall the headline: slicing bytes copies, wrapping in memoryview does not. Knowing that a view points at someone else's memory, and that tobytes() is how you get a real copy back, is enough at this level.
Explain the mechanics you would use: write-through on a bytearray versus read-only views of bytes, cast changing what len() counts, and release ending the export. Be ready to say why the resize raises BufferError.
Demonstrate judgement about when the copy actually hurts — large frames, hot parse loops, handing regions to native code — and about release discipline, since a forgotten view both pins memory and locks the buffer against growth.
Own the tradeoff as an interface decision: exposing views across a module boundary exports lifetime and mutability obligations to callers. Decide where buffers are frozen with tobytes(), and treat toreadonly() as the contract when sharing writable memory.
### Slicing copies; viewing does not `data[10:20]` on a `bytes` or `bytearray` object allocates a new object and memcpys ten bytes into it. That is cheap at ten bytes and ruinous at ten megabytes, and it is what `memoryview` exists to avoid. `memoryview(data)[10:20]` allocates only a small view object that records a pointer, a length, an item format and a reference to the exporting object. No payload bytes move. A `memoryview` is a *window*, not a container. It has no storage of its own. Everything it can do is defined by the **buffer protocol**, the C-level interface an object implements to say "here is my memory, here is its shape, and here is whether you may write to it". `bytes`, `bytearray`, `array.array`, `mmap.mmap` and many C extension types all export it; since Python 3.12 the concept also has a name in the standard library, `collections.abc.Buffer`. ### Writing through a view Because a view points at the original memory, assigning through a view of a **mutable** exporter edits the original: given `view = memoryview(frame)` over a `bytearray`, `view[0] = 0xFF` changes `frame`. Views of immutable exporters are read-only — `memoryview(b"x")[0] = 1` raises `TypeError: cannot modify read-only memory` — and `readonly` reports which kind you hold. `toreadonly()` hands out a read-only view of writable memory, which is how you share a buffer without granting write access. This is the whole point in parsing code: you can walk a large received frame, hand each field to a handler as a view, and let a handler that must mutate do so in place, with zero intermediate allocations. ### cast, and why len() stops meaning bytes `cast(fmt)` reinterprets the same bytes under a different item format: `view.cast("H")` treats the region as unsigned 2-byte integers, `cast("i")` as 4-byte signed ones, `cast("B")` returns to plain octets. Nothing is copied or converted — only the interpretation changes. Consequently `len(view)` counts **items, not bytes**: a 16-byte region is `len() == 16` as `"B"` and `len() == 8` after `cast("H")`. The byte count lives in `nbytes`, the per-item width in `itemsize`, and the code in `format`. `cast` requires a C-contiguous view whose current format is plain bytes, and multi-byte formats use the machine's native byte order — which is why a view cast is the wrong tool for decoding a fixed-endian wire format. ### tobytes and release `tobytes()` is the deliberate exit hatch: it copies the viewed region into a new immutable `bytes` object. Call it exactly when you *want* the copy — at an API boundary, or when you intend to keep a small field long after the big source buffer should have been freed. `release()` tears the view down immediately rather than waiting for garbage collection, and a `memoryview` also works as a context manager, so `with memoryview(buf) as view:` releases on exit. Releasing matters because a live export **locks the exporter against resizing**: while any view of a `bytearray` is outstanding, `buf += b"x"`, `buf.extend(...)` or a length-changing slice assignment raises `BufferError: Existing exports of data: object cannot be re-sized`. CPython refuses because a reallocation would move the memory and leave the view dangling. After the view is released, the same resize succeeds. Any operation on a released view raises `ValueError`. ### Reading the view itself Four attributes describe any view and are worth naming in an answer: `readonly` says whether writes are permitted, `format` gives the item code, `itemsize` the width of one item in bytes, and `nbytes` the total size of the region regardless of format. Together they let you assert what you are holding before you touch it, which matters because a view arriving from elsewhere may be read-only, may already be cast, or may be over an exporter you did not create. Views can also describe more than a flat run of bytes — `shape`, `strides` and `ndim` exist for multi-dimensional and non-contiguous buffers exported by C extensions — but `cast` and the simple slicing described here require a contiguous one-dimensional region. After `release()`, every operation on the view raises `ValueError`, so a released view fails loudly rather than reading freed memory. ### What it is not A `memoryview` is not an I/O buffer, and it is not a general-purpose array. It does not convert types, does not resize, does not own memory, and does not make a slow loop fast on its own — a per-item Python loop over a view is roughly as slow as any other Python loop. Its payoff is allocation and copy avoidance on large regions: parsing a frame in place, filling a preallocated destination with a `readinto`-style call and then handing out subviews, or passing a region into a C extension that can operate on foreign memory. Below a few kilobytes, plain slicing is usually simpler and fast enough, and the view's bookkeeping — release discipline, resize locking, item-vs-byte lengths — is real complexity you should only take on when the copy actually hurts.
- After view.cast('H'), why does len(view) change without any data changing?`len()` on a `memoryview` counts items, not bytes, and `cast` changes the item format. A 16-byte region has length 16 under the default `"B"` format and length 8 after casting to `"H"`, the 2-byte unsigned format. The bytes are untouched; only the interpretation moved. `nbytes` still reports 16 and `itemsize` reports 2. Multi-byte formats use native byte order, so `cast` is not a wire-format decoder.
- Why can't you append to a bytearray while a memoryview of it exists?Appending may reallocate the buffer, which would leave the view pointing at freed memory. CPython therefore counts outstanding exports and raises `BufferError: Existing exports of data: object cannot be re-sized` for any length-changing operation. Writing to existing positions is still allowed. Release the view — explicitly or by using it as a context manager — and the resize succeeds.
- Is a memoryview always faster than slicing?No. It avoids the copy, not the interpreter. Per-item Python access through a view is no faster than through bytes, and for small regions the view's own allocation and release bookkeeping outweigh a memcpy of a few dozen bytes. The win appears on large buffers, on repeated subslicing, and when handing a region to code that can consume foreign memory directly.
Slicing bytes is photocopying a page from a book; a memoryview is a bookmark plus a ruler laid across the original page. The bookmark is cheap, but the book has to stay on the desk.
saying these in an interview costs you the question
- Thinks memoryview copies the data it wraps
- Expects len() to report bytes after a cast
- Believes a view of a bytes object is writable
- Never releases views, then calls BufferError a bug
- Confuses memoryview with an I/O buffer object
- Assumes a view makes per-item Python loops fast