Skip to content

Commit be28ad3

Browse files
authored
Merge pull request #230 from adamreichold/borrow-from-array
Add PyArray::borrow_from_array to expose array data with ownership being tied to anyother Python object
2 parents cf839b7 + f828e48 commit be28ad3

File tree

3 files changed

+107
-6
lines changed

3 files changed

+107
-6
lines changed

src/array.rs

+66-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ use pyo3::{
66
ffi, prelude::*, type_object, types::PyAny, AsPyPointer, PyDowncastError, PyNativeType,
77
PyResult,
88
};
9-
use std::{cell::Cell, mem, os::raw::c_int, ptr, slice};
9+
use std::{
10+
cell::Cell,
11+
mem,
12+
os::raw::{c_int, c_void},
13+
ptr, slice,
14+
};
1015
use std::{iter::ExactSizeIterator, marker::PhantomData};
1116

12-
use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
17+
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
1318
use crate::dtype::{DataType, Element};
1419
use crate::error::{FromVecError, NotContiguousError, ShapeError};
1520
use crate::slice_box::SliceBox;
@@ -468,6 +473,65 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
468473
Self::from_owned_ptr(py, ptr)
469474
}
470475

476+
/// Creates a NumPy array backed by `array` and ties its ownership to the Python object `owner`.
477+
///
478+
/// # Safety
479+
///
480+
/// `owner` is set as a base object of the returned array which must not be dropped until `owner` is dropped.
481+
/// Furthermore, `array` must not be reallocated from the time this method is called and until `owner` is dropped.
482+
///
483+
/// # Example
484+
///
485+
/// ```rust
486+
/// # use pyo3::prelude::*;
487+
/// # use numpy::{ndarray::Array1, PyArray1};
488+
/// #
489+
/// #[pyclass]
490+
/// struct Owner {
491+
/// array: Array1<f64>,
492+
/// }
493+
///
494+
/// #[pymethods]
495+
/// impl Owner {
496+
/// #[getter]
497+
/// fn array<'py>(this: &'py PyCell<Self>) -> &'py PyArray1<f64> {
498+
/// let array = &this.borrow().array;
499+
///
500+
/// // SAFETY: The memory backing `array` will stay valid as long as this object is alive
501+
/// // as we do not modify `array` in any way which would cause it to be reallocated.
502+
/// unsafe { PyArray1::borrow_from_array(array, this) }
503+
/// }
504+
/// }
505+
/// ```
506+
pub unsafe fn borrow_from_array<'py, S>(array: &ArrayBase<S, D>, owner: &'py PyAny) -> &'py Self
507+
where
508+
S: Data<Elem = T>,
509+
{
510+
let (strides, dims) = (array.npy_strides(), array.raw_dim());
511+
let data_ptr = array.as_ptr();
512+
513+
let ptr = PY_ARRAY_API.PyArray_New(
514+
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
515+
dims.ndim_cint(),
516+
dims.as_dims_ptr(),
517+
T::npy_type() as c_int,
518+
strides.as_ptr() as *mut npy_intp, // strides
519+
data_ptr as *mut c_void, // data
520+
mem::size_of::<T>() as c_int, // itemsize
521+
0, // flag
522+
ptr::null_mut(), // obj
523+
);
524+
525+
mem::forget(owner.to_object(owner.py()));
526+
527+
PY_ARRAY_API.PyArray_SetBaseObject(
528+
ptr as *mut npyffi::PyArrayObject,
529+
owner as *const PyAny as *mut PyAny as *mut ffi::PyObject,
530+
);
531+
532+
Self::from_owned_ptr(owner.py(), ptr)
533+
}
534+
471535
/// Construct a new nd-dimensional array filled with 0.
472536
///
473537
/// If `is_fortran` is true, then

src/convert.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ where
158158
}
159159
}
160160

161-
enum Order {
161+
pub(crate) enum Order {
162162
Standard,
163163
Fortran,
164164
}
@@ -172,7 +172,7 @@ impl Order {
172172
}
173173
}
174174

175-
trait ArrayExt {
175+
pub(crate) trait ArrayExt {
176176
fn npy_strides(&self) -> NpyStrides;
177177
fn order(&self) -> Option<Order>;
178178
}
@@ -201,13 +201,13 @@ where
201201
}
202202

203203
/// Numpy strides with short array optimization
204-
enum NpyStrides {
204+
pub(crate) enum NpyStrides {
205205
Short([npyffi::npy_intp; 8]),
206206
Long(Vec<npyffi::npy_intp>),
207207
}
208208

209209
impl NpyStrides {
210-
fn as_ptr(&self) -> *const npy_intp {
210+
pub(crate) fn as_ptr(&self) -> *const npy_intp {
211211
match self {
212212
NpyStrides::Short(inner) => inner.as_ptr(),
213213
NpyStrides::Long(inner) => inner.as_ptr(),

tests/array.rs

+37
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,40 @@ fn dtype_from_py() {
252252
assert_eq!(dtype.get_datatype().unwrap(), numpy::DataType::Uint32);
253253
})
254254
}
255+
256+
#[test]
257+
fn borrow_from_array() {
258+
use numpy::ndarray::Array1;
259+
use pyo3::py_run;
260+
261+
#[pyclass]
262+
struct Owner {
263+
array: Array1<f64>,
264+
}
265+
266+
#[pymethods]
267+
impl Owner {
268+
#[getter]
269+
fn array<'py>(this: &'py PyCell<Self>) -> &'py PyArray1<f64> {
270+
let array = &this.borrow().array;
271+
272+
unsafe { PyArray1::borrow_from_array(array, this) }
273+
}
274+
}
275+
276+
let array = Python::with_gil(|py| {
277+
let owner = Py::new(
278+
py,
279+
Owner {
280+
array: Array1::linspace(0., 1., 10),
281+
},
282+
)
283+
.unwrap();
284+
285+
owner.getattr(py, "array").unwrap()
286+
});
287+
288+
Python::with_gil(|py| {
289+
py_run!(py, array, "assert array.shape == (10,)");
290+
});
291+
}

0 commit comments

Comments
 (0)