From ebe51280a1e803af988074f3265b15c05fc25f4f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 19 Mar 2022 11:46:40 -0400 Subject: [PATCH] Added an as_bytes method for Py This allows for obtaining a slice that's not lexically bound to the GIL which can be helpful to avoid copying. --- CHANGELOG.md | 4 ++++ src/types/bytes.rs | 18 ++++++++++++++++++ tests/test_bytes.rs | 15 +++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bded73909b..9615c5a1ef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Panic during compilation when `PYO3_CROSS_LIB_DIR` is set for some host/target combinations. [#2232](https://github.com/PyO3/pyo3/pull/2232) +### Added + +- Added `as_bytes` on `Py`. [#2235](https://github.com/PyO3/pyo3/pull/2235) + ## [0.16.2] - 2022-03-15 ### Packaging diff --git a/src/types/bytes.rs b/src/types/bytes.rs index e733582b970..375c1b12e09 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -97,6 +97,24 @@ impl PyBytes { } } +impl Py { + /// Gets the Python bytes as a byte slice. Because Python bytes are + /// immutable, the result may be used for as long as the reference to + /// `self` is held. + pub fn as_bytes<'a>(&self, _py: Python<'_>) -> &'a [u8] { + // py is required here because `PyBytes_AsString` and `PyBytes_Size` + // can both technically raise exceptions which require the GIL to be + // held. The only circumstance in which they raise is if the value + // isn't really a `PyBytes`, but better safe than sorry. + unsafe { + let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8; + let length = ffi::PyBytes_Size(self.as_ptr()) as usize; + debug_assert!(!buffer.is_null()); + std::slice::from_raw_parts(buffer, length) + } + } +} + /// This is the same way [Vec] is indexed. impl> Index for PyBytes { type Output = I::Output; diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 710677dd1a0..36ae41e4a60 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -41,3 +41,18 @@ fn test_bytearray_vec_conversion() { let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap(); py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'"); } + +#[test] +fn test_py_as_bytes() { + let pyobj: pyo3::Py; + let data: &[u8]; + + { + let gil = Python::acquire_gil(); + let py = gil.python(); + pyobj = pyo3::types::PyBytes::new(py, b"abc").into_py(py); + data = pyobj.as_bytes(py); + } + + assert_eq!(data, b"abc"); +}