Skip to content

Commit 31ca599

Browse files
authored
Merge pull request #74 from kngwyu/into-revive
Revive into_pyarray
2 parents 9dff9ff + e19e40d commit 31ca599

File tree

9 files changed

+285
-79
lines changed

9 files changed

+285
-79
lines changed

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ extern crate numpy;
9595
extern crate pyo3;
9696

9797
use ndarray::{ArrayD, ArrayViewD, ArrayViewMutD};
98-
use numpy::{IntoPyResult, PyArrayDyn, ToPyArray};
98+
use numpy::{IntoPyArray, IntoPyResult, PyArrayDyn};
9999
use pyo3::prelude::{pymodinit, PyModule, PyResult, Python};
100100

101101
#[pymodinit]
@@ -122,7 +122,7 @@ fn rust_ext(_py: Python, m: &PyModule) -> PyResult<()> {
122122
let x = x.as_array()?;
123123
// you can also specify your error context, via closure
124124
let y = y.as_array().into_pyresult_with(|| "y must be f64 array")?;
125-
Ok(axpy(a, x, y).to_pyarray(py).to_owned(py))
125+
Ok(axpy(a, x, y).into_pyarray(py).to_owned(py))
126126
}
127127

128128
// wrapper of `mult`
@@ -135,7 +135,6 @@ fn rust_ext(_py: Python, m: &PyModule) -> PyResult<()> {
135135

136136
Ok(())
137137
}
138-
139138
```
140139

141140
Contribution

example/extensions/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ extern crate numpy;
33
extern crate pyo3;
44

55
use ndarray::{ArrayD, ArrayViewD, ArrayViewMutD};
6-
use numpy::{IntoPyResult, PyArrayDyn, ToPyArray};
6+
use numpy::{IntoPyArray, IntoPyResult, PyArrayDyn};
77
use pyo3::prelude::{pymodinit, PyModule, PyResult, Python};
88

99
#[pymodinit]
@@ -28,9 +28,9 @@ fn rust_ext(_py: Python, m: &PyModule) -> PyResult<()> {
2828
) -> PyResult<PyArrayDyn<f64>> {
2929
// you can convert numpy error into PyErr via ?
3030
let x = x.as_array()?;
31-
// you can also specify your error context, via closure
31+
// you can also specify your error context, via closure
3232
let y = y.as_array().into_pyresult_with(|| "y must be f64 array")?;
33-
Ok(axpy(a, x, y).to_pyarray(py).to_owned(py))
33+
Ok(axpy(a, x, y).into_pyarray(py).to_owned(py))
3434
}
3535

3636
// wrapper of `mult`

example/setup.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,7 @@
1010
class CmdTest(TestCommand):
1111
def run(self):
1212
self.run_command("test_rust")
13-
test_files = os.listdir('./tests')
14-
ok = 0
15-
for f in test_files:
16-
_, ext = os.path.splitext(f)
17-
if ext == '.py':
18-
res = subprocess.call([sys.executable, f], cwd='./tests')
19-
ok = ok | res
20-
sys.exit(res)
13+
subprocess.check_call([sys.executable, 'test_ext.py'], cwd='./tests')
2114

2215

2316
setup_requires = ['setuptools-rust>=0.6.0']

example/tests/test_ext.py

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rust_ext import axpy, mult
33
import unittest
44

5+
56
class TestExt(unittest.TestCase):
67
"""Test class for rust functions
78
"""
@@ -11,6 +12,10 @@ def test_axpy(self):
1112
y = np.array([3.0, 3.0, 3.0])
1213
z = axpy(3.0, x, y)
1314
np.testing.assert_array_almost_equal(z, np.array([6.0, 9.0, 12.0]))
15+
x = np.array([*x, 4.0])
16+
y = np.array([*y, 3.0])
17+
z = axpy(3.0, x, y)
18+
np.testing.assert_array_almost_equal(z, np.array([6.0, 9.0, 12.0, 15.0]))
1419

1520
def test_mult(self):
1621
x = np.array([1.0, 2.0, 3.0])

src/array.rs

+85-59
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,30 @@ use std::mem;
99
use std::os::raw::c_int;
1010
use std::ptr;
1111

12-
use convert::{NpyIndex, ToNpyDims};
12+
use convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
1313
use error::{ErrorKind, IntoPyResult};
14+
use slice_box::SliceBox;
1415
use types::{NpyDataType, TypeNum};
1516

1617
/// A safe, static-typed interface for
1718
/// [NumPy ndarray](https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html).
1819
///
1920
/// # Memory location
20-
/// Numpy api allows to use a memory area allocated outside Pyhton.
2121
///
22-
/// However, we designed `PyArray` to always **owns a memory area allocated in Python's private
23-
/// heap**, where all memories are managed by GC.
22+
/// 1.`PyArray` constructed via `IntoPyArray::into_pyarray` or `PyArray::from_vec`
23+
/// or `PyArray::from_owned_array`
2424
///
25-
/// This means you always need to pay allocation cost when you create a `PyArray`, but don't need
26-
/// to fear memory leak.
25+
/// These methods don't allocate and use `Box<[T]>` as container.
26+
///
27+
/// Please take care that **you cannot use some destructive methods like `resize`,
28+
/// for this kind of array**.
29+
///
30+
/// 2.`PyArray` constructed via other methods, like `ToPyArray::to_pyarray` or `PyArray::from_slice`
31+
/// or `PyArray::from_array`.
32+
///
33+
/// These methods allocate a memory area in Python's private heap.
34+
///
35+
/// In this case, you have no restriction.
2736
///
2837
/// # Reference
2938
///
@@ -32,11 +41,6 @@ use types::{NpyDataType, TypeNum};
3241
/// See [pyo3's document](https://pyo3.rs/master/doc/pyo3/index.html#ownership-and-lifetimes)
3342
/// for the reason.
3443
///
35-
/// # Mutation
36-
/// You can do destructive changes to `PyArray` via &self methods like [`move_to`](#method.move_to).
37-
///
38-
/// About this design, see
39-
/// [pyo3's document](https://pyo3.rs/master/doc/pyo3/index.html#ownership-and-lifetimes), too.
4044
///
4145
/// # Dimension
4246
/// `PyArray` has 2 type parametes `T` and `D`. `T` represents its data type like `f32`, and `D`
@@ -264,6 +268,10 @@ impl<T, D> PyArray<T, D> {
264268
let ptr = self.as_array_ptr();
265269
(*ptr).data as *mut T
266270
}
271+
272+
pub(crate) unsafe fn copy_ptr(&self, other: *const T, len: usize) {
273+
ptr::copy_nonoverlapping(other, self.data(), len)
274+
}
267275
}
268276

269277
impl<T: TypeNum, D: Dimension> PyArray<T, D> {
@@ -305,7 +313,7 @@ impl<T: TypeNum, D: Dimension> PyArray<T, D> {
305313
unsafe { PyArray::new_(py, dims, ptr::null_mut(), flags) }
306314
}
307315

308-
unsafe fn new_<'py, ID>(
316+
pub(crate) unsafe fn new_<'py, ID>(
309317
py: Python<'py>,
310318
dims: ID,
311319
strides: *mut npy_intp,
@@ -329,6 +337,32 @@ impl<T: TypeNum, D: Dimension> PyArray<T, D> {
329337
Self::from_owned_ptr(py, ptr)
330338
}
331339

340+
pub(crate) unsafe fn from_boxed_slice<'py, ID>(
341+
py: Python<'py>,
342+
dims: ID,
343+
strides: *mut npy_intp,
344+
slice: Box<[T]>,
345+
) -> &'py Self
346+
where
347+
ID: IntoDimension<Dim = D>,
348+
{
349+
let dims = dims.into_dimension();
350+
let slice = SliceBox::new(slice);
351+
let ptr = PY_ARRAY_API.PyArray_New(
352+
PY_ARRAY_API.get_type_object(npyffi::ArrayType::PyArray_Type),
353+
dims.ndim_cint(),
354+
dims.as_dims_ptr(),
355+
T::typenum_default(),
356+
strides, // strides
357+
slice.data(), // data
358+
0, // itemsize
359+
0, // flag
360+
::std::ptr::null_mut(), //obj
361+
);
362+
PY_ARRAY_API.PyArray_SetBaseObject(ptr as *mut npyffi::PyArrayObject, slice.as_ptr());
363+
Self::from_owned_ptr(py, ptr)
364+
}
365+
332366
/// Construct a new nd-dimensional array filled with 0.
333367
///
334368
/// If `is_fortran` is true, then
@@ -362,7 +396,7 @@ impl<T: TypeNum, D: Dimension> PyArray<T, D> {
362396
}
363397
}
364398

365-
/// Construct PyArray from ndarray::Array.
399+
/// Construct PyArray from `ndarray::ArrayBase`.
366400
///
367401
/// This method allocates memory in Python's heap via numpy api, and then copies all elements
368402
/// of the array there.
@@ -372,25 +406,32 @@ impl<T: TypeNum, D: Dimension> PyArray<T, D> {
372406
/// # extern crate pyo3; extern crate numpy; #[macro_use] extern crate ndarray; fn main() {
373407
/// use numpy::PyArray;
374408
/// let gil = pyo3::Python::acquire_gil();
375-
/// let pyarray = PyArray::from_ndarray(gil.python(), &array![[1, 2], [3, 4]]);
409+
/// let pyarray = PyArray::from_array(gil.python(), &array![[1, 2], [3, 4]]);
376410
/// assert_eq!(pyarray.as_array().unwrap(), array![[1, 2], [3, 4]]);
377411
/// # }
378412
/// ```
379-
pub fn from_ndarray<'py, S>(py: Python<'py>, arr: &ArrayBase<S, D>) -> &'py Self
413+
pub fn from_array<'py, S>(py: Python<'py>, arr: &ArrayBase<S, D>) -> &'py Self
380414
where
381415
S: Data<Elem = T>,
382416
{
383-
let len = arr.len();
384-
let mut strides: Vec<_> = arr
385-
.strides()
386-
.into_iter()
387-
.map(|n| n * mem::size_of::<T>() as npy_intp)
388-
.collect();
389-
unsafe {
390-
let array = PyArray::new_(py, arr.raw_dim(), strides.as_mut_ptr() as *mut npy_intp, 0);
391-
ptr::copy_nonoverlapping(arr.as_ptr(), array.data(), len);
392-
array
393-
}
417+
ToPyArray::to_pyarray(arr, py)
418+
}
419+
420+
/// Construct PyArray from `ndarray::Array`.
421+
///
422+
/// This method uses internal `Vec` of `ndarray::Array` as numpy array.
423+
///
424+
/// # Example
425+
/// ```
426+
/// # extern crate pyo3; extern crate numpy; #[macro_use] extern crate ndarray; fn main() {
427+
/// use numpy::PyArray;
428+
/// let gil = pyo3::Python::acquire_gil();
429+
/// let pyarray = PyArray::from_owned_array(gil.python(), array![[1, 2], [3, 4]]);
430+
/// assert_eq!(pyarray.as_array().unwrap(), array![[1, 2], [3, 4]]);
431+
/// # }
432+
/// ```
433+
pub fn from_owned_array<'py>(py: Python<'py>, arr: Array<T, D>) -> &'py Self {
434+
IntoPyArray::into_pyarray(arr, py)
394435
}
395436

396437
/// Get the immutable view of the internal data of `PyArray`, as `ndarray::ArrayView`.
@@ -539,12 +580,27 @@ impl<T: TypeNum> PyArray<T, Ix1> {
539580
pub fn from_slice<'py>(py: Python<'py>, slice: &[T]) -> &'py Self {
540581
let array = PyArray::new(py, [slice.len()], false);
541582
unsafe {
542-
let src = slice.as_ptr() as *mut T;
543-
ptr::copy_nonoverlapping(src, array.data(), slice.len());
583+
array.copy_ptr(slice.as_ptr(), slice.len());
544584
}
545585
array
546586
}
547587

588+
/// Construct one-dimension PyArray from `Vec`.
589+
///
590+
/// # Example
591+
/// ```
592+
/// # extern crate pyo3; extern crate numpy; fn main() {
593+
/// use numpy::PyArray;
594+
/// let gil = pyo3::Python::acquire_gil();
595+
/// let vec = vec![1, 2, 3, 4, 5];
596+
/// let pyarray = PyArray::from_vec(gil.python(), vec);
597+
/// assert_eq!(pyarray.as_slice().unwrap(), &[1, 2, 3, 4, 5]);
598+
/// # }
599+
/// ```
600+
pub fn from_vec<'py>(py: Python<'py>, vec: Vec<T>) -> &'py Self {
601+
IntoPyArray::into_pyarray(vec, py)
602+
}
603+
548604
/// Construct one-dimension PyArray from `impl ExactSizeIterator`.
549605
///
550606
/// # Example
@@ -584,8 +640,7 @@ impl<T: TypeNum> PyArray<T, Ix1> {
584640
/// # }
585641
/// ```
586642
pub fn from_iter(py: Python, iter: impl IntoIterator<Item = T>) -> &Self {
587-
// ↓ max cached size of ndarray
588-
let mut capacity = 1024 / mem::size_of::<T>();
643+
let mut capacity = 512 / mem::size_of::<T>();
589644
let array = Self::new(py, [capacity], false);
590645
let mut length = 0;
591646
unsafe {
@@ -882,35 +937,6 @@ impl<T: TypeNum, D> PyArray<T, D> {
882937
}
883938
}
884939

885-
impl<T: TypeNum> PyArray<T, IxDyn> {
886-
/// Move the data of self into `other`, performing a data-type conversion if necessary.
887-
///
888-
/// For type safety, you have to convert `PyArray` to `PyArrayDyn` before using this method.
889-
/// # Example
890-
/// ```
891-
/// # extern crate pyo3; extern crate numpy; fn main() {
892-
/// use numpy::PyArray;
893-
/// let gil = pyo3::Python::acquire_gil();
894-
/// let pyarray_f = PyArray::arange(gil.python(), 2.0, 5.0, 1.0).into_dyn();
895-
/// let pyarray_i = PyArray::<i64, _>::new(gil.python(), [3], false);
896-
/// assert!(pyarray_f.move_to(pyarray_i).is_ok());
897-
/// assert_eq!(pyarray_i.as_slice().unwrap(), &[2, 3, 4]);
898-
/// # }
899-
pub fn move_to<U: TypeNum, D2: Dimension>(
900-
&self,
901-
other: &PyArray<U, D2>,
902-
) -> Result<(), ErrorKind> {
903-
let self_ptr = self.as_array_ptr();
904-
let other_ptr = other.as_array_ptr();
905-
let result = unsafe { PY_ARRAY_API.PyArray_MoveInto(other_ptr, self_ptr) };
906-
if result == -1 {
907-
Err(ErrorKind::dtype_cast(self, U::npy_data_type()))
908-
} else {
909-
Ok(())
910-
}
911-
}
912-
}
913-
914940
impl<T: TypeNum + AsPrimitive<f64>> PyArray<T, Ix1> {
915941
/// Return evenly spaced values within a given interval.
916942
/// Same as [numpy.arange](https://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html).

0 commit comments

Comments
 (0)