Skip to main content

pyo3/types/
slice.rs

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