memoryview() Function Complexity¶
The memoryview() function wraps a buffer exporter - bytes, bytearray,
array.array and anything else with the buffer protocol - in a view that
reads and writes the exporter's memory in place. Creating, slicing and casting
a view never copies the data; converting one back to bytes or a list
always does.
Complexity Analysis¶
Let n be the number of elements in the view across all its dimensions, k
the number of elements a slice covers, and e the cost of the exporter's own
hash(). Space excludes the exporter's buffer, which every view shares. The
bounds are for the built-in exporters, with element comparisons priced at
O(1) as they are for numbers; a Python __buffer__() or a value with its own
__eq__() costs whatever it does.
| Operation | Time | Space | Notes |
|---|---|---|---|
memoryview(obj) |
O(1) | O(1) | A view object whose size depends on ndim, not on the buffer's length; a view of a view shares the same buffer |
mv[i] |
O(1) | O(1) | Unpacks one element into a Python object; a multi-dimensional view takes a full index tuple, mv[i, j] |
mv[i] = v |
O(1) | O(1) | Writable 1-D views, or a full index tuple; a read-only view raises TypeError |
mv[a:b] |
O(1) | O(1) | Another view over the same memory, whatever k is; a step keeps it a view too |
mv[a:b] = data |
O(k) | O(1) contiguous, O(k) strided | 1-D views only. Copies k elements in; ValueError unless data holds exactly k elements of the same format |
len(mv) |
O(1) | O(1) | The first dimension |
bytes(mv), mv.tobytes() |
O(n) | O(n) | Copies every byte; a non-contiguous view is gathered element by element |
mv == other |
O(n) | O(1) | Element by element, by value: 1 as 'i' equals 1 as 'h', and NaN equals nothing. O(1) False when the shapes differ; ordering comparisons raise TypeError |
hash(mv) |
O(n + e) | O(1) C-contiguous, O(n) otherwise | Read-only views with format B, b or c; the exporter is hashed too, so a short slice of an unhashed bytes still pays for the whole bytes. Cached, so later calls are O(1) |
for x in mv, x in mv |
O(n) | O(1) | 1-D views only; in is a linear scan |
Methods¶
| Method | Time | Space | Notes |
|---|---|---|---|
tobytes(order='C') |
O(n) | O(n) | order picks the element order of the copy, not its size |
tolist() |
O(n) | O(n) | One list entry per element, nested by dimension; a 0-D view returns its single element itself |
hex(sep, bytes_per_sep) |
O(n) | O(n) | Two characters per byte; a non-contiguous view copies itself first |
cast(format, shape) |
O(1) | O(1) | Reinterprets the same memory. C-contiguous views only, one side a byte format, the total byte size unchanged, and either the source or the new shape 1-D |
toreadonly() |
O(1) | O(1) | A new read-only view of the same buffer |
release() |
O(1) | O(1) | Reading or writing through the view afterwards raises ValueError; until then a bytearray exporter cannot be resized |
count(value) |
O(n) | O(1) | 3.14+. Compares every element; 1-D views only |
index(value, start, stop) |
O(n) | O(1) | 3.14+. Stops at the first match; ValueError when absent. 1-D views only |
Attributes¶
| Attribute | Time | Space | Notes |
|---|---|---|---|
obj |
O(1) | O(1) | The exporter itself |
nbytes |
O(1) | O(1) | n * itemsize |
readonly |
O(1) | O(1) | |
format |
O(1) | O(1) | The struct format string, 'B' for bytes |
itemsize |
O(1) | O(1) | Bytes per element |
ndim |
O(1) | O(1) | |
shape, strides, suboffsets |
O(ndim) | O(ndim) | shape and strides build a new tuple on every access when ndim > 0; suboffsets is () for ordinary buffers |
contiguous, c_contiguous, f_contiguous |
O(1) | O(1) | Flags computed when the view is created |
Basic Usage¶
From Bytes¶
# O(1) - create view, no copy
b = b"hello"
mv = memoryview(b)
mv.obj is b # True
# Access elements
mv[0] # 104 (ord('h'))
mv[1:3] # <memory at 0x...> - slice is also an O(1) view
From Bytearray¶
# O(1) - create view
ba = bytearray(b"hello")
mv = memoryview(ba)
# Can modify through view
mv[0] = 72 # O(1) - changes 'h' to 'H'
print(ba) # bytearray(b'Hello')
From Array¶
# O(1) - works with array module
import array
arr = array.array('i', [1, 2, 3, 4, 5])
mv = memoryview(arr)
# Elements are the exporter's, not bytes
mv[0] # 1
mv.format # 'i'
mv.itemsize # 4
mv.nbytes # 20
Complexity Details¶
No Copying¶
# O(1) - memoryview doesn't copy data
b = b"a" * 10000
mv = memoryview(b) # O(1) - the view is the same size for any buffer
# vs creating a list copy
lst = list(b) # O(n) - one list entry per byte
Slicing¶
# O(1) - slice is just another view
ba = bytearray(b"hello world")
mv = memoryview(ba)
# Slice - also O(1), doesn't copy
world = mv[6:11]
world.obj is ba # True
# Writes through the slice land in the original
world[0] = 87 # 'W'
print(ba) # bytearray(b'hello World')
Indexing¶
# O(1) - direct memory access
mv = memoryview(b"test")
# Read element
byte_val = mv[0] # 116
# Write element (if mutable)
ba = bytearray(b"test")
mv = memoryview(ba)
mv[0] = 84 # O(1) - changes to 'T'
Hashing and Equality¶
# O(n) - both walk every element
b = b"hello"
mv = memoryview(b)
mv == b # True
mv == mv[1:] # False - O(1), the shapes differ
hash(mv) == hash(b) # True; cached, so the second call is O(1)
# A writable view cannot be hashed
try:
hash(memoryview(bytearray(b"hello")))
except ValueError:
pass # cannot hash writable memoryview object
Casting¶
# O(1) - the same bytes, read as a different element type
raw = bytes(8)
as_ints = memoryview(raw).cast('i')
len(as_ints) # 2
as_ints.nbytes # 8, unchanged
# A shape turns a flat buffer into a matrix, still without copying
grid = memoryview(bytes(range(6))).cast('B', (2, 3))
grid.tolist() # [[0, 1, 2], [3, 4, 5]] - O(n)
Common Patterns¶
Zero-Copy Data Access¶
# O(1) - no memory copy
data = bytearray(b"binary data here")
view = memoryview(data) # O(1)
# Process without copying
def process(view):
for i in range(len(view)):
print(view[i])
process(view) # O(n) - one O(1) read per element
Efficient Binary Protocol¶
# O(1) - parse binary data without copying
binary_data = b"\x01\x02\x03\x04"
view = memoryview(binary_data)
# Parse header - O(1)
header_type = view[0] # 1
header_version = view[1] # 2
# Parse payload - O(1) slice
payload = view[2:4] # <memory>
Slice Assignment¶
# O(1) - create view of mutable buffer
buffer = bytearray(1024)
view = memoryview(buffer)
# Modify through view
view[0:4] = b"HEAD" # O(k) - copies 4 bytes in
# Read back
header = bytes(view[0:4]) # O(k) to convert to bytes
# The lengths must match
try:
view[0:4] = b"HEADER"
except ValueError:
pass # lvalue and rvalue have different structures
Efficient Data Transfer¶
# O(1) - pass view instead of copying
def send_data(view):
# view is O(1) to create, no copy of the data
# Copy only when actually sending
bytes_to_send = bytes(view) # O(n)
# network.send(bytes_to_send)
data = b"large data" * 1000
view = memoryview(data) # O(1) - instant
# send_data(view) # Efficient
Performance Patterns¶
vs Copying¶
# Inefficient - copying
data = b"x" * 10**6
copy = data[100:200] # O(k) - creates new bytes
# Efficient - memoryview
view = memoryview(data) # O(1)
slice_view = view[100:200] # O(1) - just a view
vs List Conversion¶
# List conversion - O(n)
b = b"hello"
lst = list(b) # [104, 101, 108, 108, 111]
# Memoryview - O(1)
mv = memoryview(b) # O(1)
mv[0] # 104
Batch Processing¶
def process_chunk(chunk):
return sum(chunk) # O(k) over the chunk's elements
# O(n) - process without copying
def process_chunks(data):
mv = memoryview(data) # O(1)
# Process in chunks - O(n) total
total = 0
for i in range(0, len(mv), 1024):
chunk = mv[i:i+1024] # O(1) per chunk - just view
total += process_chunk(chunk)
return total
data = b"x" * 1000000
process_chunks(data) # Efficient - no copies
Practical Examples¶
Binary File Processing¶
import tempfile
with tempfile.TemporaryFile() as f:
f.write(b"MAGC\x02" + bytes(11) + b"payload")
f.seek(0)
data = f.read() # O(n) - the file is read once
mv = memoryview(data) # O(1)
# Access header without copying
magic = bytes(mv[0:4]) # O(k) - only copy what is needed
version = mv[4] # O(1)
# Process payload - O(1) view creation
payload = mv[16:] # O(1)
Network Protocol Parser¶
# O(1) - parse protocol messages
def parse_header(data):
view = memoryview(data) # O(1)
# Extract fields - all O(1)
msg_type = view[0]
length = int.from_bytes(view[1:3], 'big')
flags = view[3]
return {
'type': msg_type,
'length': length,
'flags': flags
}
packet = b"\x01\x00\x10\xFF" + b"payload..."
header = parse_header(packet) # {'type': 1, 'length': 16, 'flags': 255}
Efficient Buffer Sharing¶
# O(1) - share buffer without copying
def fill_buffer(view, value):
for i in range(len(view)):
view[i] = value
buffer = bytearray(1000)
view = memoryview(buffer) # O(1)
fill_buffer(view, 0) # Fill with zeros - O(n)
# buffer is now filled
Edge Cases¶
Empty Memoryview¶
# O(1)
mv = memoryview(b"") # <memory at 0x...>
len(mv) # 0
Single Byte¶
# O(1)
mv = memoryview(b"a")
mv[0] # 97
Immutable View¶
# O(1) - view of bytes (immutable)
mv = memoryview(b"hello")
# Cannot modify
try:
mv[0] = 72
except TypeError:
pass # cannot modify read-only memory
Mutable View¶
# O(1) - view of bytearray (mutable)
ba = bytearray(b"hello")
mv = memoryview(ba)
# Can modify
mv[0] = 72 # O(1) - 'H'
print(ba) # bytearray(b'Hello')
Memory Sharing¶
# O(1) - modifications visible in original
ba = bytearray(b"test")
mv = memoryview(ba)
# Modify through view
mv[0] = 84 # 'T'
# Changes visible in original
print(ba) # bytearray(b'Test')
# Changes also visible in view
print(mv[0]) # 84
Releasing a View¶
# O(1) - a live view pins a bytearray's size
ba = bytearray(b"test")
mv = memoryview(ba)
try:
ba.append(33)
except BufferError:
pass # Existing exports of data: object cannot be re-sized
mv.release() # O(1)
ba.append(33) # fine now
try:
mv[0]
except ValueError:
pass # operation forbidden on released memoryview object
Conversion Operations¶
# O(n) - convert memoryview to bytes
data = b"hello"
mv = memoryview(data)
# Convert to bytes
b = bytes(mv) # O(n) - creates copy
# b'hello'
# Convert to list
lst = list(mv) # O(n)
# [104, 101, 108, 108, 111]
# Hex string - two characters per byte
mv.hex() # '68656c6c6f'
Limitations¶
# O(1) - fast, but limited flexibility
mv = memoryview(b"hello")
# Can't concatenate directly
try:
mv + mv
except TypeError:
pass
# Must convert to bytes first
result = bytes(mv) + bytes(mv) # O(n)
# Can't append - a view has a fixed size
hasattr(mv, "append") # False
Best Practices¶
✅ Do:
- Use memoryview for zero-copy access
- Create memoryview to pass to functions efficiently
- Use slicing for efficient sub-ranges
- Convert to bytes only when necessary
- Release a view of a
bytearraybefore resizing it
❌ Avoid:
- Creating a view for one short, one-off slice - the view object is bigger
than a short
bytescopy - Assuming memoryview works like list (different API)
- Trying to modify immutable buffers (bytes)
- Hashing a writable view, or one over
bytearray- both raise
Related Functions¶
- bytes() - Immutable bytes
- bytearray() - Mutable bytes
- array - Typed array module
Version Notes¶
- Python 3.12+: exporters can be written in Python with
__buffer__()and__release_buffer__();len()of a 0-dimensional view raisesTypeError - Python 3.14+:
count()andindex();memoryview[int]is a generic alias