pyo3/types/
slice.rs

1use crate::err::{PyErr, PyResult};
2use crate::ffi;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::types::any::PyAnyMethods;
5use crate::types::{PyRange, PyRangeMethods};
6use crate::{Bound, IntoPyObject, PyAny, Python};
7use std::convert::Infallible;
8
9/// Represents a Python `slice`.
10///
11/// Values of this type are accessed via PyO3's smart pointers, e.g. as
12/// [`Py<PySlice>`][crate::Py] or [`Bound<'py, PySlice>`][Bound].
13///
14/// For APIs available on `slice` objects, see the [`PySliceMethods`] trait which is implemented for
15/// [`Bound<'py, PySlice>`][Bound].
16///
17/// Only `isize` indices supported at the moment by the `PySlice` object.
18#[repr(transparent)]
19pub struct PySlice(PyAny);
20
21pyobject_native_type!(
22    PySlice,
23    ffi::PySliceObject,
24    pyobject_native_static_type_object!(ffi::PySlice_Type),
25    #checkfunction=ffi::PySlice_Check
26);
27
28/// Return value from [`PySliceMethods::indices`].
29#[derive(Debug, Eq, PartialEq)]
30pub struct PySliceIndices {
31    /// Start of the slice
32    ///
33    /// It can be -1 when the step is negative, otherwise it's non-negative.
34    pub start: isize,
35    /// End of the slice
36    ///
37    /// It can be -1 when the step is negative, otherwise it's non-negative.
38    pub stop: isize,
39    /// Increment to use when iterating the slice from `start` to `stop`.
40    pub step: isize,
41    /// The length of the slice calculated from the original input sequence.
42    pub slicelength: usize,
43}
44
45impl PySliceIndices {
46    /// Creates a new `PySliceIndices`.
47    pub fn new(start: isize, stop: isize, step: isize) -> PySliceIndices {
48        PySliceIndices {
49            start,
50            stop,
51            step,
52            slicelength: 0,
53        }
54    }
55}
56
57impl PySlice {
58    /// Constructs a new slice with the given elements.
59    pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
60        unsafe {
61            ffi::PySlice_New(
62                ffi::PyLong_FromSsize_t(start),
63                ffi::PyLong_FromSsize_t(stop),
64                ffi::PyLong_FromSsize_t(step),
65            )
66            .assume_owned(py)
67            .downcast_into_unchecked()
68        }
69    }
70
71    /// Constructs a new full slice that is equivalent to `::`.
72    pub fn full(py: Python<'_>) -> Bound<'_, PySlice> {
73        unsafe {
74            ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
75                .assume_owned(py)
76                .downcast_into_unchecked()
77        }
78    }
79}
80
81/// Implementation of functionality for [`PySlice`].
82///
83/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
84/// syntax these methods are separated into a trait, because stable Rust does not yet support
85/// `arbitrary_self_types`.
86#[doc(alias = "PySlice")]
87pub trait PySliceMethods<'py>: crate::sealed::Sealed {
88    /// Retrieves the start, stop, and step indices from the slice object,
89    /// assuming a sequence of length `length`, and stores the length of the
90    /// slice in its `slicelength` member.
91    fn indices(&self, length: isize) -> PyResult<PySliceIndices>;
92}
93
94impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
95    fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
96        unsafe {
97            let mut slicelength: isize = 0;
98            let mut start: isize = 0;
99            let mut stop: isize = 0;
100            let mut step: isize = 0;
101            let r = ffi::PySlice_GetIndicesEx(
102                self.as_ptr(),
103                length,
104                &mut start,
105                &mut stop,
106                &mut step,
107                &mut slicelength,
108            );
109            if r == 0 {
110                Ok(PySliceIndices {
111                    start,
112                    stop,
113                    step,
114                    // non-negative isize should always fit into usize
115                    slicelength: slicelength as _,
116                })
117            } else {
118                Err(PyErr::fetch(self.py()))
119            }
120        }
121    }
122}
123
124impl<'py> IntoPyObject<'py> for PySliceIndices {
125    type Target = PySlice;
126    type Output = Bound<'py, Self::Target>;
127    type Error = Infallible;
128
129    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
130        Ok(PySlice::new(py, self.start, self.stop, self.step))
131    }
132}
133
134impl<'py> IntoPyObject<'py> for &PySliceIndices {
135    type Target = PySlice;
136    type Output = Bound<'py, Self::Target>;
137    type Error = Infallible;
138
139    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
140        Ok(PySlice::new(py, self.start, self.stop, self.step))
141    }
142}
143
144impl<'py> TryFrom<Bound<'py, PyRange>> for Bound<'py, PySlice> {
145    type Error = PyErr;
146
147    fn try_from(range: Bound<'py, PyRange>) -> Result<Self, Self::Error> {
148        Ok(PySlice::new(
149            range.py(),
150            range.start()?,
151            range.stop()?,
152            range.step()?,
153        ))
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
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}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here