Skip to content
Python Basics: Share Binary Buffers with memoryview

Python Basics: Share Binary Buffers with memoryview

October 25, 2026

A binary parser often needs a small region of a larger buffer. A memoryview lets that region refer to the existing storage rather than copying its bytes. That can also expose shared changes, so ownership and lifetime matter.

These examples use tiny bytearrays to make the behavior visible. They are not a performance benchmark, and none reads files or contacts a service.

Prepare a scratch directory

These examples were tested on Linux with Python 3.12.14. These examples target Python 3.8 or newer, including toreadonly. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the assertions remain enabled.

Create a fresh directory and keep using the same terminal:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

If either setup command fails, stop before saving any files. Save each named source below as UTF-8 in the new directory.

1. Update a region without replacing the buffer

Save as edit_buffer_region.py:

frame = bytearray(b"ID:cat")

with memoryview(frame) as whole:
    with whole[3:] as payload:
        assert payload.obj is frame
        assert payload.nbytes == 3
        payload[:] = b"dog"
        assert frame == bytearray(b"ID:dog")
        print("Shared owner:", payload.obj is frame)
        print("Payload:", payload.tobytes().decode("ascii"))
        try:
            payload[:] = b"longer"
        except ValueError:
            print("Rejected: replacement has a different size")
        else:
            raise AssertionError("The view unexpectedly resized")

print("Frame:", frame.decode("ascii"))

Run it:

python3 -B edit_buffer_region.py

Output:

Shared owner: True
Payload: dog
Rejected: replacement has a different size
Frame: ID:dog

The slice shares the bytearray’s storage. Assigning three bytes updates the original frame, while a longer replacement is rejected. The tobytes call deliberately creates a copy for display; creating the view did not require that copy. See the memoryview reference.

2. Distinguish read-only access from an immutable snapshot

Save as inspect_readonly_view.py:

buffer = bytearray(b"OPEN")

with memoryview(buffer) as writable:
    with writable.toreadonly() as readonly:
        snapshot = readonly.tobytes()
        assert readonly.readonly
        try:
            readonly[0] = ord("X")
        except TypeError:
            print("Read-only assignment: blocked")
        else:
            raise AssertionError("The read-only view accepted a write")

        buffer[:] = b"DONE"
        assert readonly.tobytes() == b"DONE"
        assert snapshot == b"OPEN"
        print("Live view:", readonly.tobytes().decode("ascii"))
        print("Earlier snapshot:", snapshot.decode("ascii"))

Run it:

python3 -B inspect_readonly_view.py

Output:

Read-only assignment: blocked
Live view: DONE
Earlier snapshot: OPEN

Read-only prevents writes through that view. It does not prevent the owner, or another writable view, from changing the same memory. Take an explicit bytes copy when later code needs the value as it was at a particular point. toreadonly changes access, not storage ownership.

3. Release a view before resizing its owner

Save as release_buffer_view.py:

buffer = bytearray(b"AB")

with memoryview(buffer) as view:
    try:
        buffer.extend(b"C")
    except BufferError:
        print("Resize while viewed: blocked")
    else:
        raise AssertionError("The bytearray resized with an active view")
    assert view.tobytes() == b"AB"

buffer.extend(b"C")
assert buffer == bytearray(b"ABC")
print("After release:", buffer.decode("ascii"))

try:
    view[0]
except ValueError:
    print("Released view: unavailable")
else:
    raise AssertionError("The released view still allowed access")

Run it:

python3 -B release_buffer_view.py

Output:

Resize while viewed: blocked
After release: ABC
Released view: unavailable

An active view temporarily prevents this bytearray from resizing. Leaving with releases the view, after which extending the owner succeeds. Accessing the released view fails. Release every outstanding view before expecting the restriction to disappear. The release method documents this lifetime rule.

Keep the scope of borrowed buffers small. These fixtures use one-dimensional byte views; other buffer providers can have different item sizes, formats, shapes, or contiguity. A memoryview is not a portable binary file format, and avoiding a copy does not make concurrent mutation safe.

Clean up

In the same terminal, remove only these sources and the empty scratch directory:

rm -- edit_buffer_region.py inspect_readonly_view.py release_buffer_view.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If rmdir reports remaining files, inspect them instead of deleting unknown contents.

References

Acknowledgements

This post was created with assistance from AI.

Last updated on