Skip to content

Commit e933e27

Browse files
authored
Merge pull request #294 from PyO3/copy-edit-convert
Simplify trait sealing and copy-edit the convert module.
2 parents a556d79 + 25d2fdc commit e933e27

File tree

4 files changed

+95
-88
lines changed

4 files changed

+95
-88
lines changed

src/array.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
401401
let mut inverted_axes = InvertedAxes::new(strides.len());
402402

403403
for i in 0..strides.len() {
404-
// TODO(kngwyu): Replace this hacky negative strides support with
404+
// FIXME(kngwyu): Replace this hacky negative strides support with
405405
// a proper constructor, when it's implemented.
406406
// See https://github.com/rust-ndarray/ndarray/issues/842 for more.
407407
if strides[i] < 0 {

src/convert.rs

Lines changed: 84 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
1-
//! Defines conversion traits between rust types and numpy data types.
1+
//! Defines conversion traits between Rust types and NumPy data types.
2+
#![deny(missing_docs)]
3+
4+
use std::{mem, os::raw::c_int};
25

36
use ndarray::{ArrayBase, Data, Dimension, IntoDimension, Ix1, OwnedRepr};
47
use pyo3::Python;
58

6-
use std::{mem, os::raw::c_int};
9+
use crate::array::PyArray;
10+
use crate::dtype::Element;
11+
use crate::npyffi::{self, npy_intp};
12+
use crate::sealed::Sealed;
713

8-
use crate::{
9-
npyffi::{self, npy_intp},
10-
Element, PyArray,
11-
};
12-
13-
/// Conversion trait from some rust types to `PyArray`.
14+
/// Conversion trait from owning Rust types into [`PyArray`].
1415
///
15-
/// This trait takes `self`, which means **it holds a pointer to Rust heap, until `resize` or other
16-
/// destructive method is called**.
16+
/// This trait takes ownership of `self`, which means it holds a pointer into the Rust heap.
1717
///
18-
/// In addition, if you construct `PyArray` via this method,
19-
/// **you cannot use some destructive methods like `resize`.**
18+
/// In addition, some destructive methods like `resize` cannot be used with NumPy arrays constructed using this trait.
2019
///
2120
/// # Example
21+
///
2222
/// ```
2323
/// use numpy::{PyArray, IntoPyArray};
24-
/// pyo3::Python::with_gil(|py| {
24+
/// use pyo3::Python;
25+
///
26+
/// Python::with_gil(|py| {
2527
/// let py_array = vec![1, 2, 3].into_pyarray(py);
28+
///
2629
/// assert_eq!(py_array.readonly().as_slice().unwrap(), &[1, 2, 3]);
27-
/// assert!(py_array.resize(100).is_err()); // You can't resize owned-by-rust array.
30+
///
31+
/// // Array cannot be resized when its data is owned by Rust.
32+
/// assert!(py_array.resize(100).is_err());
2833
/// });
2934
/// ```
3035
pub trait IntoPyArray {
36+
/// The element type of resulting array.
3137
type Item: Element;
38+
/// The dimension type of the resulting array.
3239
type Dim: Dimension;
33-
fn into_pyarray<'py>(self, _: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim>;
40+
41+
/// Consumes `self` and moves its data into a NumPy array.
42+
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim>;
3443
}
3544

3645
impl<T: Element> IntoPyArray for Box<[T]> {
3746
type Item = T;
3847
type Dim = Ix1;
48+
3949
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
4050
let dims = [self.len()];
4151
let strides = [mem::size_of::<T>() as npy_intp];
@@ -47,6 +57,7 @@ impl<T: Element> IntoPyArray for Box<[T]> {
4757
impl<T: Element> IntoPyArray for Vec<T> {
4858
type Item = T;
4959
type Dim = Ix1;
60+
5061
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
5162
let dims = [self.len()];
5263
let strides = [mem::size_of::<T>() as npy_intp];
@@ -62,49 +73,58 @@ where
6273
{
6374
type Item = A;
6475
type Dim = D;
76+
6577
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
6678
PyArray::from_owned_array(py, self)
6779
}
6880
}
6981

70-
/// Conversion trait from rust types to `PyArray`.
82+
/// Conversion trait from borrowing Rust types to [`PyArray`].
83+
///
84+
/// This trait takes `&self` by reference, which means it allocates in Python heap and then copies the elements there.
85+
///
86+
/// # Examples
7187
///
72-
/// This trait takes `&self`, which means **it allocates in Python heap and then copies
73-
/// elements there**.
74-
/// # Example
7588
/// ```
7689
/// use numpy::{PyArray, ToPyArray};
77-
/// pyo3::Python::with_gil(|py| {
90+
/// use pyo3::Python;
91+
///
92+
/// Python::with_gil(|py| {
7893
/// let py_array = vec![1, 2, 3].to_pyarray(py);
94+
///
7995
/// assert_eq!(py_array.readonly().as_slice().unwrap(), &[1, 2, 3]);
8096
/// });
8197
/// ```
8298
///
83-
/// This method converts a not-contiguous array to C-order contiguous array.
84-
/// # Example
99+
/// Due to copying the elments, this method converts non-contiguous arrays to C-order contiguous arrays.
100+
///
85101
/// ```
86102
/// use numpy::{PyArray, ToPyArray};
87103
/// use ndarray::{arr3, s};
88-
/// pyo3::Python::with_gil(|py| {
89-
/// let a = arr3(&[[[ 1, 2, 3], [ 4, 5, 6]],
90-
/// [[ 7, 8, 9], [10, 11, 12]]]);
91-
/// let slice = a.slice(s![.., 0..1, ..]);
92-
/// let sliced = arr3(&[[[ 1, 2, 3]],
93-
/// [[ 7, 8, 9]]]);
94-
/// let py_slice = slice.to_pyarray(py);
95-
/// assert_eq!(py_slice.readonly().as_array(), sliced);
96-
/// pyo3::py_run!(py, py_slice, "assert py_slice.flags['C_CONTIGUOUS']");
104+
/// use pyo3::Python;
105+
///
106+
/// Python::with_gil(|py| {
107+
/// let array = arr3(&[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]);
108+
/// let py_array = array.slice(s![.., 0..1, ..]).to_pyarray(py);
109+
///
110+
/// assert_eq!(py_array.readonly().as_array(), arr3(&[[[1, 2, 3]], [[7, 8, 9]]]));
111+
/// assert!(py_array.is_c_contiguous());
97112
/// });
98113
/// ```
99114
pub trait ToPyArray {
115+
/// The element type of resulting array.
100116
type Item: Element;
117+
/// The dimension type of the resulting array.
101118
type Dim: Dimension;
102-
fn to_pyarray<'py>(&self, _: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim>;
119+
120+
/// Copies the content pointed to by `&self` into a newly allocated NumPy array.
121+
fn to_pyarray<'py>(&self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim>;
103122
}
104123

105124
impl<T: Element> ToPyArray for [T] {
106125
type Item = T;
107126
type Dim = Ix1;
127+
108128
fn to_pyarray<'py>(&self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
109129
PyArray::from_slice(py, self)
110130
}
@@ -118,6 +138,7 @@ where
118138
{
119139
type Item = A;
120140
type Dim = D;
141+
121142
fn to_pyarray<'py>(&self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
122143
let len = self.len();
123144
match self.order() {
@@ -216,79 +237,74 @@ impl NpyStrides {
216237
}
217238
}
218239

219-
/// Utility trait to specify the dimention of array
220-
pub trait ToNpyDims: Dimension {
240+
/// Utility trait to specify the dimensions of an array.
241+
pub trait ToNpyDims: Dimension + Sealed {
242+
#[doc(hidden)]
221243
fn ndim_cint(&self) -> c_int {
222244
self.ndim() as c_int
223245
}
246+
#[doc(hidden)]
224247
fn as_dims_ptr(&self) -> *mut npyffi::npy_intp {
225248
self.slice().as_ptr() as *mut npyffi::npy_intp
226249
}
250+
#[doc(hidden)]
227251
fn to_npy_dims(&self) -> npyffi::PyArray_Dims {
228252
npyffi::PyArray_Dims {
229253
ptr: self.as_dims_ptr(),
230254
len: self.ndim_cint(),
231255
}
232256
}
233-
fn __private__(&self) -> PrivateMarker;
234257
}
235258

236-
impl<D: Dimension> ToNpyDims for D {
237-
fn __private__(&self) -> PrivateMarker {
238-
PrivateMarker
239-
}
240-
}
259+
impl<D> ToNpyDims for D where D: Dimension {}
241260

242-
/// Types that can be used to index an array.
261+
/// Trait implemented by types that can be used to index an array.
243262
///
244-
/// See
245-
/// [IntoDimension](https://docs.rs/ndarray/latest/ndarray/dimension/conversion/trait.IntoDimension.html)
246-
/// for what types you can use as `NpyIndex`.
263+
/// This is equivalent to [`ndarray::NdIndex`] but accounts for
264+
/// NumPy strides being in units of bytes instead of elements.
247265
///
248-
/// But basically, you can use
249-
/// - [tuple](https://doc.rust-lang.org/nightly/std/primitive.tuple.html)
250-
/// - [array](https://doc.rust-lang.org/nightly/std/primitive.array.html)
251-
/// - [slice](https://doc.rust-lang.org/nightly/std/primitive.slice.html)
252-
// Since Numpy's strides is byte offset, we can't use ndarray::NdIndex directly here.
253-
pub trait NpyIndex: IntoDimension {
266+
/// All types which implement [`IntoDimension`] implement this trait as well.
267+
/// This includes at least
268+
/// - [tuple](https://doc.rust-lang.org/stable/std/primitive.tuple.html)
269+
/// - [array](https://doc.rust-lang.org/stable/std/primitive.array.html)
270+
/// - [slice](https://doc.rust-lang.org/stable/std/primitive.slice.html)
271+
pub trait NpyIndex: IntoDimension + Sealed {
272+
#[doc(hidden)]
254273
fn get_checked<T>(self, dims: &[usize], strides: &[isize]) -> Option<isize>;
274+
#[doc(hidden)]
255275
fn get_unchecked<T>(self, strides: &[isize]) -> isize;
256-
fn __private__(self) -> PrivateMarker;
257276
}
258277

278+
impl<D: IntoDimension> Sealed for D {}
279+
259280
impl<D: IntoDimension> NpyIndex for D {
260281
fn get_checked<T>(self, dims: &[usize], strides: &[isize]) -> Option<isize> {
261-
let indices_ = self.into_dimension();
262-
let indices = indices_.slice();
282+
let indices = self.into_dimension();
283+
let indices = indices.slice();
284+
263285
if indices.len() != dims.len() {
264286
return None;
265287
}
266288
if indices.iter().zip(dims).any(|(i, d)| i >= d) {
267289
return None;
268290
}
269-
Some(get_unchecked_impl(
270-
indices,
271-
strides,
272-
mem::size_of::<T>() as isize,
273-
))
291+
292+
Some(get_unchecked_impl::<T>(indices, strides))
274293
}
294+
275295
fn get_unchecked<T>(self, strides: &[isize]) -> isize {
276-
let indices_ = self.into_dimension();
277-
let indices = indices_.slice();
278-
get_unchecked_impl(indices, strides, mem::size_of::<T>() as isize)
279-
}
280-
fn __private__(self) -> PrivateMarker {
281-
PrivateMarker
296+
let indices = self.into_dimension();
297+
let indices = indices.slice();
298+
get_unchecked_impl::<T>(indices, strides)
282299
}
283300
}
284301

285-
fn get_unchecked_impl(indices: &[usize], strides: &[isize], size: isize) -> isize {
302+
fn get_unchecked_impl<T>(indices: &[usize], strides: &[isize]) -> isize {
303+
let size = mem::size_of::<T>() as isize;
304+
286305
indices
287306
.iter()
288307
.zip(strides)
289308
.map(|(&i, stride)| stride * i as isize / size)
290309
.sum()
291310
}
292-
293-
#[doc(hidden)]
294-
pub struct PrivateMarker;

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ mod doctest {
7878
doc_comment!(include_str!("../README.md"), readme);
7979
}
8080

81+
mod sealed {
82+
pub trait Sealed {}
83+
}
84+
8185
/// Create a [`PyArray`] with one, two or three dimensions.
8286
///
8387
/// This macro is backed by [`ndarray::array`].

src/npyiter.rs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use crate::npyffi::{
3737
NPY_ITER_ZEROSIZE_OK,
3838
};
3939
use crate::readonly::PyReadonlyArray;
40+
use crate::sealed::Sealed;
4041

4142
/// Flags for constructing an iterator.
4243
///
@@ -89,23 +90,8 @@ impl NpyIterFlag {
8990
mod itermode {
9091
use super::*;
9192

92-
pub struct PrivateGuard;
93-
macro_rules! private_decl {
94-
() => {
95-
fn __private__() -> PrivateGuard;
96-
};
97-
}
98-
macro_rules! private_impl {
99-
() => {
100-
fn __private__() -> PrivateGuard {
101-
PrivateGuard
102-
}
103-
};
104-
}
105-
10693
/// A combinator type that represents the mode of an iterator.
107-
pub trait MultiIterMode {
108-
private_decl!();
94+
pub trait MultiIterMode: Sealed {
10995
type Pre: MultiIterMode;
11096
const FLAG: npy_uint32 = 0;
11197
fn flags() -> Vec<npy_uint32> {
@@ -120,7 +106,6 @@ mod itermode {
120106
}
121107

122108
impl MultiIterMode for () {
123-
private_impl!();
124109
type Pre = ();
125110
}
126111

@@ -130,14 +115,16 @@ mod itermode {
130115
/// Represents the iterator mode where the last array is readwrite.
131116
pub struct RW<S: MultiIterMode>(PhantomData<S>);
132117

118+
impl<S: MultiIterMode> Sealed for RO<S> {}
119+
133120
impl<S: MultiIterMode> MultiIterMode for RO<S> {
134-
private_impl!();
135121
type Pre = S;
136122
const FLAG: npy_uint32 = NPY_ITER_READONLY;
137123
}
138124

125+
impl<S: MultiIterMode> Sealed for RW<S> {}
126+
139127
impl<S: MultiIterMode> MultiIterMode for RW<S> {
140-
private_impl!();
141128
type Pre = S;
142129
const FLAG: npy_uint32 = NPY_ITER_READWRITE;
143130
}

0 commit comments

Comments
 (0)