memoryview: slicing a large buffer without copying it
A parser that reads a 20 MB response and repeatedly does data = data[10:] to consume a header copies almost the whole buffer every time. With bytes objects, a slice is a new object with its own copy of the data.
The measurement
On one machine with Python 3.14, taking 200 slices of a 20 MB bytes object with data[10:] took about 565 ms. Taking 200 slices of a memoryview of the same data took about 0.03 ms. The memory view slices do no copying; they are small objects that point into the original buffer with an offset and a length. The absolute times will change on other hardware, but the ratio shows what is going on.

Where this helps in practice
- Parsing binary protocols and file formats: walk through the buffer with slices and
struct.unpack_from(fmt, view, offset), which reads straight from the view. - Network code: read into a preallocated
bytearraywithsock.recv_into(view[filled:])instead ofrecv, which allocates a new bytes object for every call.readintoon files does the same. - Feeding parts of a large payload to hash functions or compressors without creating intermediate slices.
Things that catch people
A memoryview keeps the underlying buffer alive and locked. If you try to resize a bytearray while a view of it exists, Python raises BufferError: Existing exports of data: object cannot be re-sized. Release the view with view.release() or a with view: block first.
Views of bytes are read-only. You need a bytearray (or another writable buffer) to write through the view.
memoryview.cast() reinterprets the bytes as another type, for example view.cast('H') for unsigned 16-bit values. It uses the machine's native byte order, so for data with a defined endianness, prefer struct with an explicit < or >.
Finally, do not turn a view back into bytes casually. bytes(view[a:b]) copies, which is right when you need an independent value that outlives the buffer, and wasteful when you were about to pass it to something that accepts any buffer.
Inspecting a view instead of guessing
A memoryview carries metadata about the buffer it wraps, which is worth reading before reaching for cast() or slicing further: view.nbytes gives the total size, view.itemsize and view.format describe the element type ('B' for unsigned bytes by default), and view.contiguous tells you whether the memory is laid out without gaps, which matters if you pass it to code that assumes a flat buffer. bytes(view) and view.tobytes() do the same thing: they copy, and are the correct way to get an independent value once you actually need one.
Views also compose with the array module and with NumPy without copying. memoryview(numpy_array) works when the array is contiguous, and struct.unpack_from reads directly from it exactly as it does from a plain buffer, which is a convenient way to parse a binary format into an array's backing memory instead of into a separate Python list.