Back to Blog
Python

Python Memoryview Slicing: Views, Syntax, and Pitfalls

python memoryview slicing: Learn how memoryview slicing works in Python: view creation, index rules, performance impact, and common pitfalls when slicing binary buffers.

memoryviewslicingbuffer protocolbinary dataperformance
Diagram showing a memoryview slice referencing a contiguous buffer without copying data.

When you slice a Python memoryview, you get a new memoryview that references the same underlying buffer. That behavior makes slicing a cheap way to work with a subset of binary data. However, it also means you need to understand exactly how indices are resolved and when a slice still holds a reference to the original buffer. The syntax and runtime behavior of python memoryview slicing are the focus here.

How memoryview slicing works

A memoryview is an object that exposes a buffer's data without copying it. Slicing a memoryview returns a new memoryview that references the same buffer, but with a restricted range. The slice operation uses the same syntax as list or tuple slicing: mv[start:stop:step]. The result is another memoryview, not a list or bytes.

data = bytearray(b'abcdefgh') mv = memoryview(data) sub = mv[2:6] print(sub.tobytes()) # b'cdef'

Here sub is a memoryview that points to bytes at positions 2 through 5 of the original buffer. No copy of the data is made. The data object is still the owner of the buffer, and sub is a separate view.

Slicing syntax and index rules

The slice parameters follow the same rules as for other sequence types in Python. start, stop, and step are all optional. Negative indices are counted from the end of the view. A step value other than 1 creates a view with non-contiguous elements, which has important consequences for how you read and write data.

mv = memoryview(b'0123456789') print(mv[::2].tobytes()) # b'02468' print(mv[-3:].tobytes()) # b'789' print(mv[8:2:-2].tobytes()) # b'864'

When you use a negative step, the view is reversed relative to the original buffer. The resulting memoryview still references the same buffer, but its logical order is reversed. You can iterate over it, but the underlying data is not physically reordered.

Slicing returns a view, not a copy

The most important distinction is that slicing a memoryview does not copy the data. It creates a new view object that shares the same buffer. This has two immediate consequences.

First, writes through the slice modify the original buffer. If you change sub[0], the corresponding byte in data changes. Second, the slice keeps the original buffer alive as long as the slice exists. If you hold a slice of a large buffer, the whole buffer remains in memory, even if you only need a small portion.

data = bytearray(b'abcdef') mv = memoryview(data) sub = mv[1:3] sub[0] = ord('X') print(data) # bytearray(b'aXcdef')

This behavior is useful when you want to edit a region in place, but it can be a source of memory pressure if you store many slices of a large buffer.

Multi-dimensional memoryviews and slicing

Memoryviews can be multi-dimensional when the underlying object has a structured format, such as a numpy array or a ctypes array. In that case, slicing behaves differently. A slice of a multi-dimensional memoryview returns a view with reduced dimensions, and the indices are applied to the first dimension only unless you use multiple indices.

import array arr = array.array('i', [1, 2, 3, 4, 5, 6]) mv = memoryview(arr) # Slicing a 1D memoryview is straightforward row = mv[2:5]

For true multi-dimensional memoryviews, you need to use a format that specifies shape, such as '3i' or a nested format. Slicing such a view with a single index returns a sub-view that drops that dimension. For example, a memoryview with shape (2, 3) sliced with [1] returns a view of the second row with shape (3,). This is analogous to NumPy's behavior, but the exact rules depend on the format and the buffer protocol.

If you are working with binary data that is flat, you usually do not need multi-dimensional slicing. For structured binary records, you can use struct or array with a defined format, then slice the resulting memoryview as a one-dimensional sequence.

Performance and memory behavior of slicing

The main performance advantage of memoryview slicing is that it avoids copying. Creating a slice is an O(1) operation because it only stores the buffer pointer, offset, and length. This makes it attractive for parsing large binary files or network packets where you want to isolate a header or a field without copying the entire payload.

However, the view's non-contiguous step slices have a performance cost. When you use a step other than 1, the view is not C-contiguous. Operations that iterate over the view, such as tobytes(), may need to handle the stride and can be slower than operating on a contiguous slice. If you need a contiguous copy for a downstream API, you may have to call tobytes() or cast() to obtain a contiguous representation.

Memory usage is also affected by the fact that a slice keeps the original buffer alive. If you create a slice of a large memory-mapped file and then discard the original memoryview, the slice still holds a reference to the buffer, so the file remains mapped. This is usually fine, but it can lead to unexpected memory consumption if you keep many small slices of a large buffer.

Common slicing mistakes and how to avoid them

One common mistake is assuming that slicing a memoryview creates an independent copy. As shown earlier, writes through the slice affect the original buffer. If you need a copy, use tobytes() or bytes(mv) to create a new bytes object.

Another mistake is using a step slice with a format that expects contiguous data. For example, if you try to cast a non-contiguous memoryview to a structured format, you may get an error because the cast requires a C-contiguous buffer. The cast() method raises TypeError if the view is not contiguous.

Also, be careful with negative indices and steps. The semantics are the same as for lists, but because the view references a buffer, out-of-range indices are handled differently. Slicing never raises IndexError; it simply returns an empty view if the range is empty. But if you try to access an element via mv[index] and the index is out of bounds, you get IndexError.

mv = memoryview(b'abc') print(mv[10:20].tobytes()) # b'' # mv[10] would raise IndexError

Finally, when you slice a memoryview that was created from a mutable object, such as a bytearray, the slice remains valid as long as the original object is alive. If the original object is resized, the memoryview may become invalid. For example, if you have a bytearray and you append data after creating a memoryview, the memoryview still points to the old buffer, but the buffer may have been reallocated. This can lead to undefined behavior or a ValueError when you try to access the view. To avoid this, do not hold memoryviews across operations that may resize the underlying object.

Using memoryview slicing for binary data parsing

A practical use of memoryview slicing is parsing binary protocols without copying the entire payload. Suppose you receive a network packet as a bytes object. You can create a memoryview and slice out the header and body fields.

packet = b'\x01\x02\x03\x04payload' mv = memoryview(packet) header = mv[:4] body = mv[4:] # Interpret header as two 16-bit integers import struct a, b = struct.unpack('!HH', header)

Because header and body are views, no copy of the packet is made. If you need to modify the payload before sending it onward, you can create a bytearray and use the same slicing technique to edit specific fields in place.

This pattern is especially useful when you are processing many packets in a loop. Creating a memoryview for each packet is cheap, and slicing avoids the allocation cost of bytes slicing, which always creates a new bytes object.

For more complex binary formats, you can combine memoryview slicing with struct.iter_unpack or memoryview.cast to interpret the data as a sequence of fixed-size records. The key is to keep the view contiguous when you need to cast it, and to be aware that a slice is not a copy.

python memoryview slicing: Practical Usage and Code Examples | RYUSLOG DEV