Copying large binary values can become an invisible performance cost in Python applications. Network packets, images, mapped files, and numeric arrays often move through several layers, and every conversion to a new bytes object may allocate memory and duplicate data. memoryview provides another option: it exposes a view over any object that supports the buffer protocol, so code can inspect or modify regions without copying the whole block.
This guide explains how to create memory views, slice buffers without copies, work with formats and dimensions, modify mutable data, combine views with array, mmap, struct, and sockets, and avoid lifecycle problems.
Understanding the buffer protocol
The buffer protocol lets an object expose its underlying memory to other Python components. Built-in types such as bytes, bytearray, and array.array support it, as do many scientific and native-extension objects.
A regular slice of bytes creates a new object. A slice of a memoryview normally creates another view that references the same storage.
data = b"abcdefghijklmnopqrstuvwxyz"
view = memoryview(data)
segment = view[5:10]
print(bytes(segment)) # b'fghij'
print(segment.obj is data) # TrueThis is useful when a parser or pipeline needs to inspect several parts of a large buffer without allocating temporary objects.
Read-only and writable views
A view inherits the mutability of its source. A view over bytes is read-only. A view over bytearray can update the original storage.
buffer = bytearray(b"ABCDE")
view = memoryview(buffer)
view[1:4] = b"xyz"
print(buffer) # bytearray(b'AxyzE')The assigned data must match the selected structure. A memory view is not a resizable list; it represents an existing region with a defined format and size.
Zero-copy slicing
Nested slices continue to share the same backing object. That makes it possible to return packet fields as views instead of copying each field.
def split_packet(packet: bytes):
view = memoryview(packet)
version = view[0:1]
length = view[1:5]
payload = view[5:]
return version, length, payloadBecause the source remains exported, resizable objects cannot change size while active views exist.
data = bytearray(b"1234")
view = memoryview(data)
# data.extend(b"5") # BufferError
view.release()
data.extend(b"5")Formats, itemsize, and total bytes
A memory view can describe elements larger than one byte. A view over array('I'), for example, exposes unsigned integers rather than unrelated bytes.
from array import array
numbers = array('I', [10, 20, 30, 40])
view = memoryview(numbers)
print(view.format)
print(view.itemsize)
print(view.nbytes)
print(view.tolist())len(view) generally counts elements in the first dimension, while nbytes reports the total byte size.
Reinterpreting data with cast
cast() changes how the same bytes are viewed. It does not numerically convert values or allocate a transformed copy.
data = bytearray(range(16))
flat = memoryview(data)
matrix = flat.cast('B', shape=[4, 4])
print(matrix[2, 3])
matrix[0, 0] = 255
print(data[0])The total number of bytes must remain compatible. For structured binary layouts, combine views with Python struct, which handles field formats, offsets, and byte order.
Memory-mapped files
A memory-mapped file can expose file contents as a buffer. Views then represent specific regions without reading the entire file into separate objects.
import mmap
with open("data.bin", "r+b") as file:
with mmap.mmap(file.fileno(), 0) as mapping:
view = memoryview(mapping)
header = view[:64]
print(bytes(header[:8]))
header.release()
view.release()Release every view before closing the mapping. Otherwise, Python may raise BufferError. See the detailed guide to Python mmap.
Receiving socket data into reusable buffers
Socket APIs such as recv_into() can write directly into an existing buffer.
buffer = bytearray(65536)
view = memoryview(buffer)
# received = sock.recv_into(view)
# packet = view[:received]Reusable buffers can reduce allocation pressure in high-throughput servers. The design must ensure that a region is not reused while another task still reads it. Pool ownership and handoff rules should be explicit.
memoryview versus bytes
Use bytes when you need an independent, immutable value that is easy to store, hash, send, or keep beyond the source lifetime. Use memoryview when you need zero-copy slicing, in-place edits, or direct interoperability with buffer-aware APIs.
Calling bytes(view) creates a copy. Convert only at the boundary where independence is actually required.
Lifecycle and ownership rules
- Do not resize a
bytearraywhile views are exported. - Release views before closing an
mmapor native resource. - Do not retain a view indefinitely when its buffer comes from a reusable pool.
- Document whether an API returns owned data or borrowed shared memory.
- Use
with memoryview(obj) as viewwhen deterministic release improves clarity.
A binary parser without intermediate copies
import struct
HEADER = struct.Struct("!BI")
def parse_packet(data: bytes):
view = memoryview(data)
if view.nbytes < HEADER.size:
raise ValueError("incomplete packet")
kind, size = HEADER.unpack_from(view, 0)
start = HEADER.size
end = start + size
if end > view.nbytes:
raise ValueError("truncated payload")
payload = view[start:end]
return kind, payloadThe returned payload borrows memory from data. The caller can inspect it directly and create an owned copy only when persistence or asynchronous storage requires one.
When optimization is worthwhile
For small scripts and tiny values, ordinary bytes slices are usually simpler. Memory views matter in binary pipelines, networking, multimedia processing, large-file analysis, scientific libraries, and native integrations.
Measure before changing an architecture. Tools such as tracemalloc can reveal whether temporary copies meaningfully affect memory. Zero-copy code also introduces ownership constraints, so the performance gain should justify the added discipline.
Conclusion
memoryview is Python’s standard interface for borrowed buffer access. It supports zero-copy slices, writable views, format metadata, multidimensional casts, and integration with array, struct, mmap, and sockets.
The official Python memoryview documentation lists supported operations and format rules. Use it when copying is a measurable cost, while keeping ownership, mutability, and lifetime explicit.







