pyo3/types/
slice.rs

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