From 6b78f1b43c2f025485cee9fc45c338fe705225f7 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 2 Mar 2020 18:25:07 -0300 Subject: [PATCH 1/2] Implement `*Py*` traits for arrays (up to 32) --- CHANGELOG.md | 1 + src/types/list.rs | 32 ++++++++++++++++++- src/types/sequence.rs | 71 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6929e41a..9c691f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `FromPyObject` is now automatically implemented for `T: Clone` pyclasses. [#730](https://github.com/PyO3/pyo3/pull/730) * `#[pyo3(get)]` and `#[pyo3(set)]` will now use the Rust doc-comment from the field for the Python property. [#755](https://github.com/PyO3/pyo3/pull/755) * `#[setter]` functions may now take an argument of `Pyo3::Python`. [#760](https://github.com/PyO3/pyo3/pull/760) +* Implement `FromPyObject` and `IntoPy` traits for arrays (up to 32). [#778](https://github.com/PyO3/pyo3/pull/778) ### Fixed diff --git a/src/types/list.rs b/src/types/list.rs index b354ffbc..a6850f99 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -178,6 +178,26 @@ where } } +macro_rules! array_impls { + ($($N:expr),+) => { + $( + impl IntoPy for [T; $N] + where + T: ToPyObject + { + fn into_py(self, py: Python) -> PyObject { + self.as_ref().to_object(py) + } + } + )+ + } +} + +array_impls!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32 +); + impl ToPyObject for Vec where T: ToPyObject, @@ -209,7 +229,7 @@ mod test { use crate::objectprotocol::ObjectProtocol; use crate::types::PyList; use crate::Python; - use crate::{PyTryFrom, ToPyObject}; + use crate::{PyTryFrom, PyObject, IntoPy, ToPyObject}; #[test] fn test_new() { @@ -423,4 +443,14 @@ mod test { assert_eq!(3, list.get_item(2).extract::().unwrap()); assert_eq!(2, list.get_item(3).extract::().unwrap()); } + + #[test] + fn test_array_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let array: PyObject = [1, 2].into_py(py); + let list = ::try_from(array.as_ref(py)).unwrap(); + assert_eq!(1, list.get_item(0).extract::().unwrap()); + assert_eq!(2, list.get_item(1).extract::().unwrap()); + } } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 0de56dfa..efb7cd9d 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -2,6 +2,7 @@ use crate::buffer; use crate::err::{self, PyDowncastError, PyErr, PyResult}; +use crate::exceptions; use crate::ffi::{self, Py_ssize_t}; use crate::instance::PyNativeType; use crate::internal_tricks::Unsendable; @@ -241,6 +242,48 @@ impl PySequence { } } +macro_rules! array_impls { + ($($N:expr),+) => { + $( + impl<'a, T> FromPyObject<'a> for [T; $N] + where + T: Copy + Default + FromPyObject<'a>, + { + default fn extract(obj: &'a PyAny) -> PyResult { + let mut array = [T::default(); $N]; + extract_sequence_into_slice(obj, &mut array)?; + Ok(array) + } + } + + impl<'source, T> FromPyObject<'source> for [T; $N] + where + for<'a> T: Copy + Default + FromPyObject<'a> + buffer::Element, + { + fn extract(obj: &'source PyAny) -> PyResult { + let mut array = [T::default(); $N]; + // first try buffer protocol + if let Ok(buf) = buffer::PyBuffer::get(obj.py(), obj) { + if buf.dimensions() == 1 && buf.copy_to_slice(obj.py(), &mut array).is_ok() { + buf.release(obj.py()); + return Ok(array); + } + buf.release(obj.py()); + } + // fall back to sequence protocol + extract_sequence_into_slice(obj, &mut array)?; + Ok(array) + } + } + )+ + } +} + +array_impls!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32 +); + impl<'a, T> FromPyObject<'a> for Vec where T: FromPyObject<'a>, @@ -282,6 +325,22 @@ where Ok(v) } +fn extract_sequence_into_slice<'s, T>(obj: &'s PyAny, slice: &mut [T]) -> PyResult<()> +where + T: FromPyObject<'s>, +{ + let seq = ::try_from(obj)?; + if seq.len()? as usize != slice.len() { + return Err(exceptions::BufferError::py_err( + "Slice length does not match buffer length.", + )); + } + for (value, item) in slice.iter_mut().zip(seq.iter()?) { + *value = item?.extract::()?; + } + Ok(()) +} + impl<'v> PyTryFrom<'v> for PySequence { fn try_from>(value: V) -> Result<&'v PySequence, PyDowncastError> { let value = value.into(); @@ -640,6 +699,18 @@ mod test { assert!(v == [1, 2, 3, 4]); } + #[test] + fn test_extract_bytearray_to_array() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: [u8; 3] = py + .eval("bytearray(b'abc')", None, None) + .unwrap() + .extract() + .unwrap(); + assert!(&v == b"abc"); + } + #[test] fn test_extract_bytearray_to_vec() { let gil = Python::acquire_gil(); From 402c382f0e65fafa6dd2e54c7084a8ce7132be23 Mon Sep 17 00:00:00 2001 From: Yuji Kanagawa Date: Tue, 3 Mar 2020 13:41:15 +0900 Subject: [PATCH 2/2] Make rustfmt happy --- src/types/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/list.rs b/src/types/list.rs index a6850f99..fff6c77c 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -229,7 +229,7 @@ mod test { use crate::objectprotocol::ObjectProtocol; use crate::types::PyList; use crate::Python; - use crate::{PyTryFrom, PyObject, IntoPy, ToPyObject}; + use crate::{IntoPy, PyObject, PyTryFrom, ToPyObject}; #[test] fn test_new() {