pyo3/types/
slice.rs

1use crate::err::{PyErr, PyResult};
2use crate::ffi;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::types::any::PyAnyMethods;
5#[allow(deprecated)]
6use crate::ToPyObject;
7use crate::{Bound, IntoPyObject, PyAny, PyObject, Python};
8use std::convert::Infallible;
9
10/// Represents a Python `slice`.
11///
12/// Values of this type are accessed via PyO3's smart pointers, e.g. as
13/// [`Py<PySlice>`][crate::Py] or [`Bound<'py, PySlice>`][Bound].
14///
15/// For APIs available on `slice` objects, see the [`PySliceMethods`] trait which is implemented for
16/// [`Bound<'py, PySlice>`][Bound].
17///
18/// Only `isize` indices supported at the moment by the `PySlice` object.
19#[repr(transparent)]
20pub struct PySlice(PyAny);
21
22pyobject_native_type!(
23    PySlice,
24    ffi::PySliceObject,
25    pyobject_native_static_type_object!(ffi::PySlice_Type),
26    #checkfunction=ffi::PySlice_Check
27);
28
29/// Return value from [`PySliceMethods::indices`].
30#[derive(Debug, Eq, PartialEq)]
31pub struct PySliceIndices {
32    /// Start of the slice
33    ///
34    /// It can be -1 when the step is negative, otherwise it's non-negative.
35    pub start: isize,
36    /// End of the slice
37    ///
38    /// It can be -1 when the step is negative, otherwise it's non-negative.
39    pub stop: isize,
40    /// Increment to use when iterating the slice from `start` to `stop`.
41    pub step: isize,
42    /// The length of the slice calculated from the original input sequence.
43    pub slicelength: usize,
44}
45
46impl PySliceIndices {
47    /// Creates a new `PySliceIndices`.
48    pub fn new(start: isize, stop: isize, step: isize) -> PySliceIndices {
49        PySliceIndices {
50            start,
51            stop,
52            step,
53            slicelength: 0,
54        }
55    }
56}
57
58impl PySlice {
59    /// Constructs a new slice with the given elements.
60    pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
61        unsafe {
62            ffi::PySlice_New(
63                ffi::PyLong_FromSsize_t(start),
64                ffi::PyLong_FromSsize_t(stop),
65                ffi::PyLong_FromSsize_t(step),
66            )
67            .assume_owned(py)
68            .downcast_into_unchecked()
69        }
70    }
71
72    /// Deprecated name for [`PySlice::new`].
73    #[deprecated(since = "0.23.0", note = "renamed to `PySlice::new`")]
74    #[inline]
75    pub fn new_bound(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
76        Self::new(py, start, stop, step)
77    }
78
79    /// Constructs a new full slice that is equivalent to `::`.
80    pub fn full(py: Python<'_>) -> Bound<'_, PySlice> {
81        unsafe {
82            ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
83                .assume_owned(py)
84                .downcast_into_unchecked()
85        }
86    }
87
88    /// Deprecated name for [`PySlice::full`].
89    #[deprecated(since = "0.23.0", note = "renamed to `PySlice::full`")]
90    #[inline]
91    pub fn full_bound(py: Python<'_>) -> Bound<'_, PySlice> {
92        Self::full(py)
93    }
94}
95
96/// Implementation of functionality for [`PySlice`].
97///
98/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
99/// syntax these methods are separated into a trait, because stable Rust does not yet support
100/// `arbitrary_self_types`.
101#[doc(alias = "PySlice")]
102pub trait PySliceMethods<'py>: crate::sealed::Sealed {
103    /// Retrieves the start, stop, and step indices from the slice object,
104    /// assuming a sequence of length `length`, and stores the length of the
105    /// slice in its `slicelength` member.
106    fn indices(&self, length: isize) -> PyResult<PySliceIndices>;
107}
108
109impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
110    fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
111        unsafe {
112            let mut slicelength: isize = 0;
113            let mut start: isize = 0;
114            let mut stop: isize = 0;
115            let mut step: isize = 0;
116            let r = ffi::PySlice_GetIndicesEx(
117                self.as_ptr(),
118                length,
119                &mut start,
120                &mut stop,
121                &mut step,
122                &mut slicelength,
123            );
124            if r == 0 {
125                Ok(PySliceIndices {
126                    start,
127                    stop,
128                    step,
129                    // non-negative isize should always fit into usize
130                    slicelength: slicelength as _,
131                })
132            } else {
133                Err(PyErr::fetch(self.py()))
134            }
135        }
136    }
137}
138
139#[allow(deprecated)]
140impl ToPyObject for PySliceIndices {
141    fn to_object(&self, py: Python<'_>) -> PyObject {
142        PySlice::new(py, self.start, self.stop, self.step).into()
143    }
144}
145
146impl<'py> IntoPyObject<'py> for PySliceIndices {
147    type Target = PySlice;
148    type Output = Bound<'py, Self::Target>;
149    type Error = Infallible;
150
151    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
152        Ok(PySlice::new(py, self.start, self.stop, self.step))
153    }
154}
155
156impl<'py> IntoPyObject<'py> for &PySliceIndices {
157    type Target = PySlice;
158    type Output = Bound<'py, Self::Target>;
159    type Error = Infallible;
160
161    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
162        Ok(PySlice::new(py, self.start, self.stop, self.step))
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_py_slice_new() {
172        Python::with_gil(|py| {
173            let slice = PySlice::new(py, isize::MIN, isize::MAX, 1);
174            assert_eq!(
175                slice.getattr("start").unwrap().extract::<isize>().unwrap(),
176                isize::MIN
177            );
178            assert_eq!(
179                slice.getattr("stop").unwrap().extract::<isize>().unwrap(),
180                isize::MAX
181            );
182            assert_eq!(
183                slice.getattr("step").unwrap().extract::<isize>().unwrap(),
184                1
185            );
186        });
187    }
188
189    #[test]
190    fn test_py_slice_full() {
191        Python::with_gil(|py| {
192            let slice = PySlice::full(py);
193            assert!(slice.getattr("start").unwrap().is_none(),);
194            assert!(slice.getattr("stop").unwrap().is_none(),);
195            assert!(slice.getattr("step").unwrap().is_none(),);
196            assert_eq!(
197                slice.indices(0).unwrap(),
198                PySliceIndices {
199                    start: 0,
200                    stop: 0,
201                    step: 1,
202                    slicelength: 0,
203                },
204            );
205            assert_eq!(
206                slice.indices(42).unwrap(),
207                PySliceIndices {
208                    start: 0,
209                    stop: 42,
210                    step: 1,
211                    slicelength: 42,
212                },
213            );
214        });
215    }
216
217    #[test]
218    fn test_py_slice_indices_new() {
219        let start = 0;
220        let stop = 0;
221        let step = 0;
222        assert_eq!(
223            PySliceIndices::new(start, stop, step),
224            PySliceIndices {
225                start,
226                stop,
227                step,
228                slicelength: 0
229            }
230        );
231
232        let start = 0;
233        let stop = 100;
234        let step = 10;
235        assert_eq!(
236            PySliceIndices::new(start, stop, step),
237            PySliceIndices {
238                start,
239                stop,
240                step,
241                slicelength: 0
242            }
243        );
244
245        let start = 0;
246        let stop = -10;
247        let step = -1;
248        assert_eq!(
249            PySliceIndices::new(start, stop, step),
250            PySliceIndices {
251                start,
252                stop,
253                step,
254                slicelength: 0
255            }
256        );
257
258        let start = 0;
259        let stop = -10;
260        let step = 20;
261        assert_eq!(
262            PySliceIndices::new(start, stop, step),
263            PySliceIndices {
264                start,
265                stop,
266                step,
267                slicelength: 0
268            }
269        );
270    }
271}