Skip to content

Add PyMemoryView::from_owned_buffer for zero-copy memoryview creation#5937

Open
alex wants to merge 1 commit intoPyO3:mainfrom
alex:claude/implement-pyo3-api-5871-kzVaG
Open

Add PyMemoryView::from_owned_buffer for zero-copy memoryview creation#5937
alex wants to merge 1 commit intoPyO3:mainfrom
alex:claude/implement-pyo3-api-5871-kzVaG

Conversation

@alex
Copy link
Copy Markdown
Member

@alex alex commented Apr 2, 2026

Adds a new method to create a Python memoryview that exposes a read-only view of byte data owned by a frozen PyClass instance without copying. This is useful for libraries like pyca/cryptography that need to expose internal buffers efficiently.

The method uses PyBuffer_FillInfo + PyMemoryView_FromBuffer to create a memoryview backed by the owner's data, with the owner kept alive via the buffer's obj reference.

Safety is enforced at compile time:

  • T: PyClass<Frozen = True> prevents mutation that could invalidate pointers
  • for<'a> FnOnce(&'a T) -> &'a [u8] ensures the slice borrows from T or is 'static

Closes #5871

https://claude.ai/code/session_01EEP1DaqJwHGCoNufi2JT9H

@alex alex force-pushed the claude/implement-pyo3-api-5871-kzVaG branch 2 times, most recently from ac672ac to 800c7dc Compare April 2, 2026 13:05
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 2, 2026

Merging this PR will not alter performance

✅ 105 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing alex:claude/implement-pyo3-api-5871-kzVaG (ddec6dd) with main (dd63a6a)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@alex alex force-pushed the claude/implement-pyo3-api-5871-kzVaG branch 2 times, most recently from 710f6b2 to 2353d5d Compare April 2, 2026 23:12
@alex
Copy link
Copy Markdown
Member Author

alex commented Apr 2, 2026

netlify stuff looks unrelated?

@davidhewitt
Copy link
Copy Markdown
Member

Indeed, hopefully #5939 will resolve that.

@davidhewitt
Copy link
Copy Markdown
Member

#5941 might make the call_method_one_arg benchmark more stable...

@alex alex force-pushed the claude/implement-pyo3-api-5871-kzVaG branch from 2353d5d to fba259b Compare April 3, 2026 12:27
@alex
Copy link
Copy Markdown
Member Author

alex commented Apr 4, 2026

No luck with netlify...

@davidhewitt
Copy link
Copy Markdown
Member

#5945 has finally got things green, one more rebase / merge should fix.

@alex alex force-pushed the claude/implement-pyo3-api-5871-kzVaG branch 2 times, most recently from 4de5647 to 7f86fc8 Compare April 4, 2026 18:31
Copy link
Copy Markdown
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for moving this forward and sorry to be a touch slow to review. Please forgive me for being slightly critical in comments, as per the OP hinting this was generated by Claude I do feel that the code has a somewhat AI-bloaty feel.

// Force GC to ensure the owner is kept alive by the memoryview
py.run(c"import gc; gc.collect()", None, None).unwrap();
let bytes: Vec<u8> = view.call_method0("tobytes").unwrap().extract().unwrap();
assert_eq!(bytes, b"kept alive");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We perhaps need to test the opposite, that after the MemoryView is dropped the owner is also freed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am generally trending towards having tests inside the library, gated on cfg(feature = "macros") where necessary. Perhaps can do the same here?

Comment on lines +104 to +106
// SAFETY: PyMemoryView_FromBuffer creates a memoryview that takes
// ownership of the buffer (it will call PyBuffer_Release when the
// memoryview is deallocated, which will decref view.obj).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this definitely the case? I don't see documentation or tests which demonstrate that view is stolen by the call.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure it is with respect to the Py_buffer.... however in going back to triple check I spotted https://github.com/python/cpython/blob/main/Objects/memoryobject.c#L769-L788 which I'm now confused/concerned about the correctness.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, turns out CPython throws away the owner 🙃. Going to file this upstream.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, good catch! I guess possibly also relevant is https://github.com/python/cpython/blob/d24ce178a2a413d3b5bb032264434e8cb416d8fe/Objects/memoryobject.c#L832 - the implementation of __buffer__ wrapper for bf_getbuffer implementations.

That might hint that an alternative implementation we could use is to support __buffer__ in #[pymethods] (we would like that anyway, see #3148). We could perhaps allow two signatures:

  • low-level signature equivalent to our current __getbuffer__ method (or maybe with small tweaks)
  • fn __buffer__(&self) -> &[u8], i.e. the equivalent of what's here, but packaged up to call PyBuffer_FillInfo somewhere in PyO3 machinery

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so convinced there's value in adding tests here if we already added tests at the Rust level.

Adds a new method to create a Python memoryview that exposes a read-only
view of byte data owned by a frozen PyClass instance without copying.
This is useful for libraries like pyca/cryptography that need to expose
internal buffers efficiently.

The method uses PyBuffer_FillInfo + PyMemoryView_FromBuffer to create a
memoryview backed by the owner's data, with the owner kept alive via
the buffer's obj reference.

Safety is enforced at compile time:
- T: PyClass<Frozen = True> prevents mutation that could invalidate pointers
- for<'a> FnOnce(&'a T) -> &'a [u8] ensures the slice borrows from T or is 'static

Closes PyO3#5871

https://claude.ai/code/session_01EEP1DaqJwHGCoNufi2JT9H
@alex
Copy link
Copy Markdown
Member Author

alex commented Apr 13, 2026

Thanks for the review -- sorry I hadn't caught the sloppy comments prior to putting this up.

I think this may be on pause while I work through the CPython bug, or figure out an alternative path. Which is frustrating, because conceptually the buffer machinery supports everything we need for this...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API for returning a memoryview pointing at memory in a Py<T>

3 participants