Skip to content

Commit dc5fa95

Browse files
committed
Add new untyped PyUntypedArray base type of PyArray.
1 parent d58add3 commit dc5fa95

File tree

4 files changed

+306
-170
lines changed

4 files changed

+306
-170
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
- Unreleased
4+
- Add `PyUntypedArray` as an untyped base type for `PyArray` which can be used to inspect arguments before more targeted downcasts. This is accompanied by some methods like `dtype` and `shape` moving from `PyArray` to `PyUntypedArray`. They are still accessible though, as `PyArray` dereferences to `PyUntypedArray` via the `Deref` trait. ([#369](https://github.com/PyO3/rust-numpy/pull/369))
45
- Drop deprecated `PyArray::from_exact_iter` as it does not provide any benefits over `PyArray::from_iter`. ([#370](https://github.com/PyO3/rust-numpy/pull/370))
56

67
- v0.18.0

src/array.rs

Lines changed: 44 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use std::{
66
marker::PhantomData,
77
mem,
8+
ops::Deref,
89
os::raw::{c_int, c_void},
910
ptr, slice,
1011
};
@@ -16,21 +17,22 @@ use ndarray::{
1617
};
1718
use num_traits::AsPrimitive;
1819
use pyo3::{
19-
ffi, pyobject_native_type_named, types::PyModule, AsPyPointer, FromPyObject, IntoPy, Py, PyAny,
20+
ffi, pyobject_native_type_base, types::PyModule, AsPyPointer, FromPyObject, IntoPy, Py, PyAny,
2021
PyClassInitializer, PyDowncastError, PyErr, PyNativeType, PyObject, PyResult, PyTypeInfo,
2122
Python, ToPyObject,
2223
};
2324

2425
use crate::borrow::{PyReadonlyArray, PyReadwriteArray};
2526
use crate::cold;
2627
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
27-
use crate::dtype::{Element, PyArrayDescr};
28+
use crate::dtype::Element;
2829
use crate::error::{
2930
BorrowError, DimensionalityError, FromVecError, IgnoreError, NotContiguousError, TypeError,
3031
DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR,
3132
};
3233
use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
3334
use crate::slice_container::PySliceContainer;
35+
use crate::untyped_array::PyUntypedArray;
3436

3537
/// A safe, statically-typed wrapper for NumPy's [`ndarray`][ndarray] class.
3638
///
@@ -135,93 +137,64 @@ unsafe impl<T: Element, D: Dimension> PyTypeInfo for PyArray<T, D> {
135137
}
136138
}
137139

138-
pyobject_native_type_named!(PyArray<T, D>; T; D);
140+
pyobject_native_type_base!(PyArray<T, D>; T; D);
139141

140-
impl<T, D> IntoPy<PyObject> for PyArray<T, D> {
141-
fn into_py(self, py: Python<'_>) -> PyObject {
142-
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
142+
impl<T, D> AsRef<PyAny> for PyArray<T, D> {
143+
#[inline]
144+
fn as_ref(&self) -> &PyAny {
145+
&self.0
143146
}
144147
}
145148

146-
impl<'py, T: Element, D: Dimension> FromPyObject<'py> for &'py PyArray<T, D> {
147-
fn extract(ob: &'py PyAny) -> PyResult<Self> {
148-
PyArray::extract(ob)
149+
impl<T, D> Deref for PyArray<T, D> {
150+
type Target = PyUntypedArray;
151+
152+
#[inline]
153+
fn deref(&self) -> &Self::Target {
154+
unsafe { &*(self as *const Self as *const PyUntypedArray) }
149155
}
150156
}
151157

152-
impl<T, D> PyArray<T, D> {
153-
/// Returns a raw pointer to the underlying [`PyArrayObject`][npyffi::PyArrayObject].
158+
impl<T, D> AsPyPointer for PyArray<T, D> {
154159
#[inline]
155-
pub fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject {
156-
self.as_ptr() as _
160+
fn as_ptr(&self) -> *mut ffi::PyObject {
161+
self.0.as_ptr()
157162
}
163+
}
158164

159-
/// Returns the `dtype` of the array.
160-
///
161-
/// See also [`ndarray.dtype`][ndarray-dtype] and [`PyArray_DTYPE`][PyArray_DTYPE].
162-
///
163-
/// # Example
164-
///
165-
/// ```
166-
/// use numpy::{dtype, PyArray};
167-
/// use pyo3::Python;
168-
///
169-
/// Python::with_gil(|py| {
170-
/// let array = PyArray::from_vec(py, vec![1_i32, 2, 3]);
171-
///
172-
/// assert!(array.dtype().is_equiv_to(dtype::<i32>(py)));
173-
/// });
174-
/// ```
175-
///
176-
/// [ndarray-dtype]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.dtype.html
177-
/// [PyArray_DTYPE]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_DTYPE
178-
pub fn dtype(&self) -> &PyArrayDescr {
179-
unsafe {
180-
let descr_ptr = (*self.as_array_ptr()).descr;
181-
self.py().from_borrowed_ptr(descr_ptr as _)
182-
}
165+
impl<T, D> IntoPy<Py<PyArray<T, D>>> for &'_ PyArray<T, D> {
166+
#[inline]
167+
fn into_py(self, py: Python<'_>) -> Py<PyArray<T, D>> {
168+
unsafe { Py::from_borrowed_ptr(py, self.as_ptr()) }
183169
}
170+
}
184171

185-
#[inline(always)]
186-
pub(crate) fn check_flags(&self, flags: c_int) -> bool {
187-
unsafe { (*self.as_array_ptr()).flags & flags != 0 }
172+
impl<T, D> From<&'_ PyArray<T, D>> for Py<PyArray<T, D>> {
173+
#[inline]
174+
fn from(other: &PyArray<T, D>) -> Self {
175+
unsafe { Py::from_borrowed_ptr(other.py(), other.as_ptr()) }
188176
}
177+
}
189178

190-
/// Returns `true` if the internal data of the array is contiguous,
191-
/// indepedently of whether C-style/row-major or Fortran-style/column-major.
192-
///
193-
/// # Example
194-
///
195-
/// ```
196-
/// use numpy::PyArray1;
197-
/// use pyo3::{types::IntoPyDict, Python};
198-
///
199-
/// Python::with_gil(|py| {
200-
/// let array = PyArray1::arange(py, 0, 10, 1);
201-
/// assert!(array.is_contiguous());
202-
///
203-
/// let view = py
204-
/// .eval("array[::2]", None, Some([("array", array)].into_py_dict(py)))
205-
/// .unwrap()
206-
/// .downcast::<PyArray1<i32>>()
207-
/// .unwrap();
208-
/// assert!(!view.is_contiguous());
209-
/// });
210-
/// ```
211-
pub fn is_contiguous(&self) -> bool {
212-
self.check_flags(npyffi::NPY_ARRAY_C_CONTIGUOUS | npyffi::NPY_ARRAY_F_CONTIGUOUS)
179+
impl<'a, T, D> From<&'a PyArray<T, D>> for &'a PyAny {
180+
fn from(ob: &'a PyArray<T, D>) -> Self {
181+
unsafe { &*(ob as *const PyArray<T, D> as *const PyAny) }
213182
}
183+
}
214184

215-
/// Returns `true` if the internal data of the array is Fortran-style/column-major contiguous.
216-
pub fn is_fortran_contiguous(&self) -> bool {
217-
self.check_flags(npyffi::NPY_ARRAY_F_CONTIGUOUS)
185+
impl<T, D> IntoPy<PyObject> for PyArray<T, D> {
186+
fn into_py(self, py: Python<'_>) -> PyObject {
187+
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
218188
}
189+
}
219190

220-
/// Returns `true` if the internal data of the array is C-style/row-major contiguous.
221-
pub fn is_c_contiguous(&self) -> bool {
222-
self.check_flags(npyffi::NPY_ARRAY_C_CONTIGUOUS)
191+
impl<'py, T: Element, D: Dimension> FromPyObject<'py> for &'py PyArray<T, D> {
192+
fn extract(ob: &'py PyAny) -> PyResult<Self> {
193+
PyArray::extract(ob)
223194
}
195+
}
224196

197+
impl<T, D> PyArray<T, D> {
225198
/// Turn `&PyArray<T,D>` into `Py<PyArray<T,D>>`,
226199
/// i.e. a pointer into Python's heap which is independent of the GIL lifetime.
227200
///
@@ -264,105 +237,6 @@ impl<T, D> PyArray<T, D> {
264237
py.from_borrowed_ptr(ptr)
265238
}
266239

267-
/// Returns the number of dimensions of the array.
268-
///
269-
/// See also [`ndarray.ndim`][ndarray-ndim] and [`PyArray_NDIM`][PyArray_NDIM].
270-
///
271-
/// # Example
272-
///
273-
/// ```
274-
/// use numpy::PyArray3;
275-
/// use pyo3::Python;
276-
///
277-
/// Python::with_gil(|py| {
278-
/// let arr = PyArray3::<f64>::zeros(py, [4, 5, 6], false);
279-
///
280-
/// assert_eq!(arr.ndim(), 3);
281-
/// });
282-
/// ```
283-
///
284-
/// [ndarray-ndim]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.ndim.html
285-
/// [PyArray_NDIM]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_NDIM
286-
#[inline]
287-
pub fn ndim(&self) -> usize {
288-
unsafe { (*self.as_array_ptr()).nd as usize }
289-
}
290-
291-
/// Returns a slice indicating how many bytes to advance when iterating along each axis.
292-
///
293-
/// See also [`ndarray.strides`][ndarray-strides] and [`PyArray_STRIDES`][PyArray_STRIDES].
294-
///
295-
/// # Example
296-
///
297-
/// ```
298-
/// use numpy::PyArray3;
299-
/// use pyo3::Python;
300-
///
301-
/// Python::with_gil(|py| {
302-
/// let arr = PyArray3::<f64>::zeros(py, [4, 5, 6], false);
303-
///
304-
/// assert_eq!(arr.strides(), &[240, 48, 8]);
305-
/// });
306-
/// ```
307-
/// [ndarray-strides]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html
308-
/// [PyArray_STRIDES]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_STRIDES
309-
#[inline]
310-
pub fn strides(&self) -> &[isize] {
311-
let n = self.ndim();
312-
if n == 0 {
313-
cold();
314-
return &[];
315-
}
316-
let ptr = self.as_array_ptr();
317-
unsafe {
318-
let p = (*ptr).strides;
319-
slice::from_raw_parts(p, n)
320-
}
321-
}
322-
323-
/// Returns a slice which contains dimmensions of the array.
324-
///
325-
/// See also [`ndarray.shape`][ndaray-shape] and [`PyArray_DIMS`][PyArray_DIMS].
326-
///
327-
/// # Example
328-
///
329-
/// ```
330-
/// use numpy::PyArray3;
331-
/// use pyo3::Python;
332-
///
333-
/// Python::with_gil(|py| {
334-
/// let arr = PyArray3::<f64>::zeros(py, [4, 5, 6], false);
335-
///
336-
/// assert_eq!(arr.shape(), &[4, 5, 6]);
337-
/// });
338-
/// ```
339-
///
340-
/// [ndarray-shape]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.shape.html
341-
/// [PyArray_DIMS]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_DIMS
342-
#[inline]
343-
pub fn shape(&self) -> &[usize] {
344-
let n = self.ndim();
345-
if n == 0 {
346-
cold();
347-
return &[];
348-
}
349-
let ptr = self.as_array_ptr();
350-
unsafe {
351-
let p = (*ptr).dimensions as *mut usize;
352-
slice::from_raw_parts(p, n)
353-
}
354-
}
355-
356-
/// Calculates the total number of elements in the array.
357-
pub fn len(&self) -> usize {
358-
self.shape().iter().product()
359-
}
360-
361-
/// Returns `true` if the there are no elements in the array.
362-
pub fn is_empty(&self) -> bool {
363-
self.shape().iter().any(|dim| *dim == 0)
364-
}
365-
366240
/// Returns a pointer to the first element of the array.
367241
#[inline(always)]
368242
pub fn data(&self) -> *mut T {
@@ -401,7 +275,7 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
401275
Ok(array)
402276
}
403277

404-
/// Same as [`shape`][Self::shape], but returns `D` insead of `&[usize]`.
278+
/// Same as [`shape`][PyUntypedArray::shape], but returns `D` insead of `&[usize]`.
405279
#[inline(always)]
406280
pub fn dims(&self) -> D {
407281
D::from_dimension(&Dim(self.shape())).expect(DIMENSIONALITY_MISMATCH_ERR)
@@ -1499,7 +1373,7 @@ impl<T: Element, D> PyArray<T, D> {
14991373

15001374
/// Extends or truncates the dimensions of an array.
15011375
///
1502-
/// This method works only on [contiguous][`Self::is_contiguous`] arrays.
1376+
/// This method works only on [contiguous][PyUntypedArray::is_contiguous] arrays.
15031377
/// Missing elements will be initialized as if calling [`zeros`][Self::zeros].
15041378
///
15051379
/// See also [`ndarray.resize`][ndarray-resize] and [`PyArray_Resize`][PyArray_Resize].

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ mod error;
8383
pub mod npyffi;
8484
mod slice_container;
8585
mod sum_products;
86+
mod untyped_array;
8687

8788
pub use ndarray;
8889
pub use pyo3;
@@ -105,6 +106,7 @@ pub use crate::dtype::{dtype, Complex32, Complex64, Element, PyArrayDescr};
105106
pub use crate::error::{BorrowError, FromVecError, NotContiguousError};
106107
pub use crate::npyffi::{PY_ARRAY_API, PY_UFUNC_API};
107108
pub use crate::sum_products::{dot, einsum, inner};
109+
pub use crate::untyped_array::PyUntypedArray;
108110

109111
pub use ndarray::{array, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn};
110112

0 commit comments

Comments
 (0)