diff --git a/guide/src/migration.md b/guide/src/migration.md index 1abb7202e4d..e0bd38a5153 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -92,6 +92,46 @@ where ``` +### Macro conversion changed for byte collections (`Vec`, `[u8; N]` and `SmallVec<[u8; N]>`). +
+Click to expand + +PyO3 0.23 introduced the new fallible conversion trait `IntoPyObject`. The `#[pyfunction]` and +`#[pymethods]` macros prefer `IntoPyObject` implementations over `IntoPy`. + +This change has an effect on functions and methods returning _byte_ collections like +- `Vec` +- `[u8; N]` +- `SmallVec<[u8; N]>` + +In their new `IntoPyObject` implementation these will now turn into `PyBytes` rather than a +`PyList`. All other `T`s are unaffected and still convert into a `PyList`. + +```rust +# #![allow(dead_code)] +# use pyo3::prelude::*; +#[pyfunction] +fn foo() -> Vec { // would previously turn into a `PyList`, now `PyBytes` + vec![0, 1, 2, 3] +} + +#[pyfunction] +fn bar() -> Vec { // unaffected, returns `PyList` + vec![0, 1, 2, 3] +} +``` + +If this conversion is _not_ desired, consider building a list manually using `PyList::new`. + +The following types were previously _only_ implemented for `u8` and now allow other `T`s turn into +`PyList` +- `&[T]` +- `Cow<[T]>` + +This is purely additional and should just extend the possible return types. + +
+ ## from 0.21.* to 0.22 ### Deprecation of `gil-refs` feature continues diff --git a/newsfragments/4442.changed.md b/newsfragments/4442.changed.md new file mode 100644 index 00000000000..44fbcbfe23c --- /dev/null +++ b/newsfragments/4442.changed.md @@ -0,0 +1,2 @@ +`IntoPyObject` impls for `Vec`, `&[u8]`, `[u8; N]`, `Cow<[u8]>` and `SmallVec<[u8; N]>` now +convert into `PyBytes` rather than `PyList`. \ No newline at end of file diff --git a/pyo3-benches/Cargo.toml b/pyo3-benches/Cargo.toml index e99ef09e19c..7a7f17d276b 100644 --- a/pyo3-benches/Cargo.toml +++ b/pyo3-benches/Cargo.toml @@ -47,6 +47,10 @@ harness = false name = "bench_gil" harness = false +[[bench]] +name = "bench_intopyobject" +harness = false + [[bench]] name = "bench_list" harness = false diff --git a/pyo3-benches/benches/bench_intopyobject.rs b/pyo3-benches/benches/bench_intopyobject.rs new file mode 100644 index 00000000000..16351c9088b --- /dev/null +++ b/pyo3-benches/benches/bench_intopyobject.rs @@ -0,0 +1,93 @@ +use std::hint::black_box; + +use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; + +use pyo3::conversion::IntoPyObject; +use pyo3::prelude::*; +use pyo3::types::PyBytes; + +fn bench_bytes_new(b: &mut Bencher<'_>, data: &[u8]) { + Python::with_gil(|py| { + b.iter_with_large_drop(|| PyBytes::new(py, black_box(data))); + }); +} + +fn bytes_new_small(b: &mut Bencher<'_>) { + bench_bytes_new(b, &[]); +} + +fn bytes_new_medium(b: &mut Bencher<'_>) { + let data = (0..u8::MAX).into_iter().collect::>(); + bench_bytes_new(b, &data); +} + +fn bytes_new_large(b: &mut Bencher<'_>) { + let data = vec![10u8; 100_000]; + bench_bytes_new(b, &data); +} + +fn bench_bytes_into_pyobject(b: &mut Bencher<'_>, data: &[u8]) { + Python::with_gil(|py| { + b.iter_with_large_drop(|| black_box(data).into_pyobject(py)); + }); +} + +fn byte_slice_into_pyobject_small(b: &mut Bencher<'_>) { + bench_bytes_into_pyobject(b, &[]); +} + +fn byte_slice_into_pyobject_medium(b: &mut Bencher<'_>) { + let data = (0..u8::MAX).into_iter().collect::>(); + bench_bytes_into_pyobject(b, &data); +} + +fn byte_slice_into_pyobject_large(b: &mut Bencher<'_>) { + let data = vec![10u8; 100_000]; + bench_bytes_into_pyobject(b, &data); +} + +fn byte_slice_into_py(b: &mut Bencher<'_>) { + Python::with_gil(|py| { + let data = (0..u8::MAX).into_iter().collect::>(); + let bytes = data.as_slice(); + b.iter_with_large_drop(|| black_box(bytes).into_py(py)); + }); +} + +fn vec_into_pyobject(b: &mut Bencher<'_>) { + Python::with_gil(|py| { + let bytes = (0..u8::MAX).into_iter().collect::>(); + b.iter_with_large_drop(|| black_box(&bytes).clone().into_pyobject(py)); + }); +} + +fn vec_into_py(b: &mut Bencher<'_>) { + Python::with_gil(|py| { + let bytes = (0..u8::MAX).into_iter().collect::>(); + b.iter_with_large_drop(|| black_box(&bytes).clone().into_py(py)); + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("bytes_new_small", bytes_new_small); + c.bench_function("bytes_new_medium", bytes_new_medium); + c.bench_function("bytes_new_large", bytes_new_large); + c.bench_function( + "byte_slice_into_pyobject_small", + byte_slice_into_pyobject_small, + ); + c.bench_function( + "byte_slice_into_pyobject_medium", + byte_slice_into_pyobject_medium, + ); + c.bench_function( + "byte_slice_into_pyobject_large", + byte_slice_into_pyobject_large, + ); + c.bench_function("byte_slice_into_py", byte_slice_into_py); + c.bench_function("vec_into_pyobject", vec_into_pyobject); + c.bench_function("vec_into_py", vec_into_py); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/src/conversion.rs b/src/conversion.rs index a5844bd5140..82710cf16fb 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -6,7 +6,7 @@ use crate::pyclass::boolean_struct::False; use crate::types::any::PyAnyMethods; use crate::types::PyTuple; use crate::{ - ffi, Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyObject, PyRef, PyRefMut, Python, + ffi, Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyErr, PyObject, PyRef, PyRefMut, Python, }; use std::convert::Infallible; @@ -200,6 +200,65 @@ pub trait IntoPyObject<'py>: Sized { /// Performs the conversion. fn into_pyobject(self, py: Python<'py>) -> Result; + + /// Converts sequence of Self into a Python object. Used to specialize `Vec`, `[u8; N]` + /// and `SmallVec<[u8; N]>` as a sequence of bytes into a `bytes` object. + #[doc(hidden)] + fn owned_sequence_into_pyobject( + iter: I, + py: Python<'py>, + _: private::Token, + ) -> Result, PyErr> + where + I: IntoIterator + AsRef<[Self]>, + I::IntoIter: ExactSizeIterator, + PyErr: From, + { + let mut iter = iter.into_iter().map(|e| { + e.into_pyobject(py) + .map(BoundObject::into_any) + .map(BoundObject::unbind) + .map_err(Into::into) + }); + let list = crate::types::list::try_new_from_iter(py, &mut iter); + list.map(Bound::into_any) + } + + /// Converts sequence of Self into a Python object. Used to specialize `&[u8]` and `Cow<[u8]>` + /// as a sequence of bytes into a `bytes` object. + #[doc(hidden)] + fn borrowed_sequence_into_pyobject( + iter: I, + py: Python<'py>, + _: private::Token, + ) -> Result, PyErr> + where + Self: private::Reference, + I: IntoIterator + AsRef<[::BaseType]>, + I::IntoIter: ExactSizeIterator, + PyErr: From, + { + let mut iter = iter.into_iter().map(|e| { + e.into_pyobject(py) + .map(BoundObject::into_any) + .map(BoundObject::unbind) + .map_err(Into::into) + }); + let list = crate::types::list::try_new_from_iter(py, &mut iter); + list.map(Bound::into_any) + } +} + +pub(crate) mod private { + pub struct Token; + + pub trait Reference { + type BaseType; + } + + impl Reference for &'_ T { + type BaseType = T; + } } impl<'py, T> IntoPyObject<'py> for Bound<'py, T> { diff --git a/src/conversions/smallvec.rs b/src/conversions/smallvec.rs index 090944d4412..091cbfc48ee 100644 --- a/src/conversions/smallvec.rs +++ b/src/conversions/smallvec.rs @@ -20,12 +20,12 @@ use crate::exceptions::PyTypeError; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::types::any::PyAnyMethods; -use crate::types::list::{new_from_iter, try_new_from_iter}; -use crate::types::{PyList, PySequence, PyString}; +use crate::types::list::new_from_iter; +use crate::types::{PySequence, PyString}; use crate::PyErr; use crate::{ - err::DowncastError, ffi, Bound, BoundObject, FromPyObject, IntoPy, PyAny, PyObject, PyResult, - Python, ToPyObject, + err::DowncastError, ffi, Bound, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, + ToPyObject, }; use smallvec::{Array, SmallVec}; @@ -62,18 +62,17 @@ where A::Item: IntoPyObject<'py>, PyErr: From<>::Error>, { - type Target = PyList; + type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; + /// Turns [`SmallVec`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`] + /// + /// [`PyBytes`]: crate::types::PyBytes + /// [`PyList`]: crate::types::PyList + #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - let mut iter = self.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::unbind) - .map_err(Into::into) - }); - try_new_from_iter(py, &mut iter) + ::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token) } } @@ -120,7 +119,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::types::PyDict; + use crate::types::{PyBytes, PyBytesMethods, PyDict, PyList}; #[test] fn test_smallvec_into_py() { @@ -162,4 +161,19 @@ mod tests { assert!(l.eq(hso).unwrap()); }); } + + #[test] + fn test_smallvec_intopyobject_impl() { + Python::with_gil(|py| { + let bytes: SmallVec<[u8; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect(); + let obj = bytes.clone().into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &*bytes); + + let nums: SmallVec<[u16; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect(); + let obj = nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + }); + } } diff --git a/src/conversions/std/array.rs b/src/conversions/std/array.rs index bae5c6eb2fa..2780868ae04 100644 --- a/src/conversions/std/array.rs +++ b/src/conversions/std/array.rs @@ -1,8 +1,7 @@ use crate::conversion::IntoPyObject; -use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; use crate::types::any::PyAnyMethods; -use crate::types::{PyList, PySequence}; +use crate::types::PySequence; use crate::{ err::DowncastError, ffi, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, @@ -41,34 +40,19 @@ where impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N] where T: IntoPyObject<'py>, + PyErr: From, { - type Target = PyList; + type Target = PyAny; type Output = Bound<'py, Self::Target>; - type Error = T::Error; + type Error = PyErr; + /// Turns [`[u8; N]`](std::array) into [`PyBytes`], all other `T`s will be turned into a [`PyList`] + /// + /// [`PyBytes`]: crate::types::PyBytes + /// [`PyList`]: crate::types::PyList + #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - use crate::BoundObject; - unsafe { - let len = N as ffi::Py_ssize_t; - - let ptr = ffi::PyList_New(len); - - // We create the `Bound` pointer here for two reasons: - // - panics if the ptr is null - // - its Drop cleans up the list if user code errors or panics. - let list = ptr.assume_owned(py).downcast_into_unchecked::(); - - for (i, obj) in (0..len).zip(self) { - let obj = obj.into_pyobject(py)?.into_ptr(); - - #[cfg(not(Py_LIMITED_API))] - ffi::PyList_SET_ITEM(ptr, i, obj); - #[cfg(Py_LIMITED_API)] - ffi::PyList_SetItem(ptr, i, obj); - } - - Ok(list) - } + T::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token) } } @@ -166,7 +150,11 @@ mod tests { sync::atomic::{AtomicUsize, Ordering}, }; - use crate::{ffi, types::any::PyAnyMethods}; + use crate::{ + conversion::IntoPyObject, + ffi, + types::{any::PyAnyMethods, PyBytes, PyBytesMethods}, + }; use crate::{types::PyList, IntoPy, PyResult, Python, ToPyObject}; #[test] @@ -257,6 +245,21 @@ mod tests { }); } + #[test] + fn test_array_intopyobject_impl() { + Python::with_gil(|py| { + let bytes: [u8; 6] = *b"foobar"; + let obj = bytes.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &bytes); + + let nums: [u16; 4] = [0, 1, 2, 3]; + let obj = nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + }); + } + #[test] fn test_extract_non_iterable_to_array() { Python::with_gil(|py| { diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 618ca8f142b..61c666a1cfe 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -1,9 +1,10 @@ +use crate::conversion::private::Reference; use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::types::any::PyAnyMethods; -use crate::types::PyInt; +use crate::types::{PyBytes, PyInt}; use crate::{ exceptions, ffi, Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, @@ -161,6 +162,16 @@ macro_rules! int_fits_c_long { } } + impl<'py> IntoPyObject<'py> for &$rust_type { + type Target = PyInt; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + (*self).into_pyobject(py) + } + } + impl<'py> FromPyObject<'py> for $rust_type { fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; @@ -176,8 +187,82 @@ macro_rules! int_fits_c_long { }; } +impl ToPyObject for u8 { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(*self as c_long)) } + } +} +impl IntoPy for u8 { + fn into_py(self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(self as c_long)) } + } + #[cfg(feature = "experimental-inspect")] + fn type_output() -> TypeInfo { + TypeInfo::builtin("int") + } +} +impl<'py> IntoPyObject<'py> for u8 { + type Target = PyInt; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + unsafe { + Ok(ffi::PyLong_FromLong(self as c_long) + .assume_owned(py) + .downcast_into_unchecked()) + } + } + + #[inline] + fn owned_sequence_into_pyobject( + iter: I, + py: Python<'py>, + _: crate::conversion::private::Token, + ) -> Result, PyErr> + where + I: AsRef<[u8]>, + { + Ok(PyBytes::new(py, iter.as_ref()).into_any()) + } +} + +impl<'py> IntoPyObject<'py> for &'_ u8 { + type Target = PyInt; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + u8::into_pyobject(*self, py) + } + + #[inline] + fn borrowed_sequence_into_pyobject( + iter: I, + py: Python<'py>, + _: crate::conversion::private::Token, + ) -> Result, PyErr> + where + // I: AsRef<[u8]>, but the compiler needs it expressed via the trait for some reason + I: AsRef<[::BaseType]>, + { + Ok(PyBytes::new(py, iter.as_ref()).into_any()) + } +} + +impl<'py> FromPyObject<'py> for u8 { + fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; + u8::try_from(val).map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) + } + + #[cfg(feature = "experimental-inspect")] + fn type_input() -> TypeInfo { + Self::type_output() + } +} + int_fits_c_long!(i8); -int_fits_c_long!(u8); int_fits_c_long!(i16); int_fits_c_long!(u16); int_fits_c_long!(i32); diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index 4c25305f777..9b9eb3dd3ec 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -1,11 +1,11 @@ -use std::{borrow::Cow, convert::Infallible}; +use std::borrow::Cow; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ conversion::IntoPyObject, types::{PyByteArray, PyByteArrayMethods, PyBytes}, - Bound, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, + Bound, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, }; impl<'a> IntoPy for &'a [u8] { @@ -19,13 +19,22 @@ impl<'a> IntoPy for &'a [u8] { } } -impl<'py> IntoPyObject<'py> for &[u8] { - type Target = PyBytes; +impl<'a, 'py, T> IntoPyObject<'py> for &'a [T] +where + &'a T: IntoPyObject<'py>, + PyErr: From<<&'a T as IntoPyObject<'py>>::Error>, +{ + type Target = PyAny; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = PyErr; + /// Turns [`&[u8]`](std::slice) into [`PyBytes`], all other `T`s will be turned into a [`PyList`] + /// + /// [`PyBytes`]: crate::types::PyBytes + /// [`PyList`]: crate::types::PyList + #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(PyBytes::new(py, self)) + <&T>::borrowed_sequence_into_pyobject(self, py, crate::conversion::private::Token) } } @@ -73,13 +82,23 @@ impl IntoPy> for Cow<'_, [u8]> { } } -impl<'py> IntoPyObject<'py> for Cow<'_, [u8]> { - type Target = PyBytes; +impl<'py, T> IntoPyObject<'py> for Cow<'_, [T]> +where + T: Clone, + for<'a> &'a T: IntoPyObject<'py>, + for<'a> PyErr: From<<&'a T as IntoPyObject<'py>>::Error>, +{ + type Target = PyAny; type Output = Bound<'py, Self::Target>; - type Error = Infallible; + type Error = PyErr; + /// Turns `Cow<[u8]>` into [`PyBytes`], all other `T`s will be turned into a [`PyList`] + /// + /// [`PyBytes`]: crate::types::PyBytes + /// [`PyList`]: crate::types::PyList + #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - Ok(PyBytes::new(py, &self)) + <&T>::borrowed_sequence_into_pyobject(self.as_ref(), py, crate::conversion::private::Token) } } @@ -88,8 +107,9 @@ mod tests { use std::borrow::Cow; use crate::{ + conversion::IntoPyObject, ffi, - types::{any::PyAnyMethods, PyBytes}, + types::{any::PyAnyMethods, PyBytes, PyBytesMethods, PyList}, Python, ToPyObject, }; @@ -127,4 +147,44 @@ mod tests { assert!(cow.bind(py).is_instance_of::()); }); } + + #[test] + fn test_slice_intopyobject_impl() { + Python::with_gil(|py| { + let bytes: &[u8] = b"foobar"; + let obj = bytes.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), bytes); + + let nums: &[u16] = &[0, 1, 2, 3]; + let obj = nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + }); + } + + #[test] + fn test_cow_intopyobject_impl() { + Python::with_gil(|py| { + let borrowed_bytes = Cow::<[u8]>::Borrowed(b"foobar"); + let obj = borrowed_bytes.clone().into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &*borrowed_bytes); + + let owned_bytes = Cow::<[u8]>::Owned(b"foobar".to_vec()); + let obj = owned_bytes.clone().into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &*owned_bytes); + + let borrowed_nums = Cow::<[u16]>::Borrowed(&[0, 1, 2, 3]); + let obj = borrowed_nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + + let owned_nums = Cow::<[u16]>::Owned(vec![0, 1, 2, 3]); + let obj = owned_nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + }); + } } diff --git a/src/conversions/std/vec.rs b/src/conversions/std/vec.rs index 9a759baeecf..40ad7eea8a0 100644 --- a/src/conversions/std/vec.rs +++ b/src/conversions/std/vec.rs @@ -1,9 +1,8 @@ use crate::conversion::IntoPyObject; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; -use crate::types::list::{new_from_iter, try_new_from_iter}; -use crate::types::PyList; -use crate::{Bound, BoundObject, IntoPy, PyErr, PyObject, Python, ToPyObject}; +use crate::types::list::new_from_iter; +use crate::{Bound, IntoPy, PyAny, PyErr, PyObject, Python, ToPyObject}; impl ToPyObject for [T] where @@ -46,18 +45,71 @@ where T: IntoPyObject<'py>, PyErr: From, { - type Target = PyList; + type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; + /// Turns [`Vec`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`] + /// + /// [`PyBytes`]: crate::types::PyBytes + /// [`PyList`]: crate::types::PyList + #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - let mut iter = self.into_iter().map(|e| { - e.into_pyobject(py) - .map(BoundObject::into_any) - .map(BoundObject::unbind) - .map_err(Into::into) + T::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token) + } +} + +impl<'a, 'py, T> IntoPyObject<'py> for &'a Vec +where + &'a T: IntoPyObject<'py>, + PyErr: From<<&'a T as IntoPyObject<'py>>::Error>, +{ + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + #[inline] + fn into_pyobject(self, py: Python<'py>) -> Result { + // NB: we could actually not cast to `PyAny`, which would be nice for + // `&Vec`, but that'd be inconsistent with the `IntoPyObject` impl + // above which always returns a `PyAny` for `Vec`. + self.as_slice().into_pyobject(py).map(Bound::into_any) + } +} + +#[cfg(test)] +mod tests { + use crate::conversion::IntoPyObject; + use crate::types::{PyAnyMethods, PyBytes, PyBytesMethods, PyList}; + use crate::Python; + + #[test] + fn test_vec_intopyobject_impl() { + Python::with_gil(|py| { + let bytes: Vec = b"foobar".to_vec(); + let obj = bytes.clone().into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &bytes); + + let nums: Vec = vec![0, 1, 2, 3]; + let obj = nums.into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); }); + } - try_new_from_iter(py, &mut iter) + #[test] + fn test_vec_reference_intopyobject_impl() { + Python::with_gil(|py| { + let bytes: Vec = b"foobar".to_vec(); + let obj = (&bytes).into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + let obj = obj.downcast_into::().unwrap(); + assert_eq!(obj.as_bytes(), &bytes); + + let nums: Vec = vec![0, 1, 2, 3]; + let obj = (&nums).into_pyobject(py).unwrap(); + assert!(obj.is_instance_of::()); + }); } } diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 876cf156ab8..397e2eb3848 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -95,6 +95,7 @@ impl PyBytes { /// }) /// # } /// ``` + #[inline] pub fn new_with(py: Python<'_>, len: usize, init: F) -> PyResult> where F: FnOnce(&mut [u8]) -> PyResult<()>, diff --git a/tests/ui/missing_intopy.stderr b/tests/ui/missing_intopy.stderr index e0818ec42ef..58ddbff0f22 100644 --- a/tests/ui/missing_intopy.stderr +++ b/tests/ui/missing_intopy.stderr @@ -11,11 +11,11 @@ error[E0277]: `Blah` cannot be converted to a Python object &'a Py &'a PyRef<'py, T> &'a PyRefMut<'py, T> + &'a Vec + &'a [T] &'a pyo3::Bound<'py, T> &OsStr &OsString - &Path - &PathBuf and $N others note: required by a bound in `UnknownReturnType::::wrap` --> src/impl_/wrap.rs