pyo3/types/
slice.rs

1use crate::err::{PyErr, PyResult};
2use crate::ffi;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::types::any::PyAnyMethods;
5#[cfg(feature = "gil-refs")]
6use crate::PyNativeType;
7use crate::{Bound, PyAny, PyObject, Python, ToPyObject};
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_bound(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_bound(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#[cfg(feature = "gil-refs")]
82impl PySlice {
83    /// Deprecated form of `PySlice::new_bound`.
84    #[deprecated(
85        since = "0.21.0",
86        note = "`PySlice::new` will be replaced by `PySlice::new_bound` in a future PyO3 version"
87    )]
88    pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> &PySlice {
89        Self::new_bound(py, start, stop, step).into_gil_ref()
90    }
91
92    /// Deprecated form of `PySlice::full_bound`.
93    #[deprecated(
94        since = "0.21.0",
95        note = "`PySlice::full` will be replaced by `PySlice::full_bound` in a future PyO3 version"
96    )]
97    pub fn full(py: Python<'_>) -> &PySlice {
98        PySlice::full_bound(py).into_gil_ref()
99    }
100
101    /// Retrieves the start, stop, and step indices from the slice object,
102    /// assuming a sequence of length `length`, and stores the length of the
103    /// slice in its `slicelength` member.
104    #[inline]
105    pub fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
106        self.as_borrowed().indices(length)
107    }
108}
109
110/// Implementation of functionality for [`PySlice`].
111///
112/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
113/// syntax these methods are separated into a trait, because stable Rust does not yet support
114/// `arbitrary_self_types`.
115#[doc(alias = "PySlice")]
116pub trait PySliceMethods<'py>: crate::sealed::Sealed {
117    /// Retrieves the start, stop, and step indices from the slice object,
118    /// assuming a sequence of length `length`, and stores the length of the
119    /// slice in its `slicelength` member.
120    fn indices(&self, length: isize) -> PyResult<PySliceIndices>;
121}
122
123impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
124    fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
125        unsafe {
126            let mut slicelength: isize = 0;
127            let mut start: isize = 0;
128            let mut stop: isize = 0;
129            let mut step: isize = 0;
130            let r = ffi::PySlice_GetIndicesEx(
131                self.as_ptr(),
132                length,
133                &mut start,
134                &mut stop,
135                &mut step,
136                &mut slicelength,
137            );
138            if r == 0 {
139                Ok(PySliceIndices {
140                    start,
141                    stop,
142                    step,
143                    // non-negative isize should always fit into usize
144                    slicelength: slicelength as _,
145                })
146            } else {
147                Err(PyErr::fetch(self.py()))
148            }
149        }
150    }
151}
152
153impl ToPyObject for PySliceIndices {
154    fn to_object(&self, py: Python<'_>) -> PyObject {
155        PySlice::new_bound(py, self.start, self.stop, self.step).into()
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_py_slice_new() {
165        Python::with_gil(|py| {
166            let slice = PySlice::new_bound(py, isize::MIN, isize::MAX, 1);
167            assert_eq!(
168                slice.getattr("start").unwrap().extract::<isize>().unwrap(),
169                isize::MIN
170            );
171            assert_eq!(
172                slice.getattr("stop").unwrap().extract::<isize>().unwrap(),
173                isize::MAX
174            );
175            assert_eq!(
176                slice.getattr("step").unwrap().extract::<isize>().unwrap(),
177                1
178            );
179        });
180    }
181
182    #[test]
183    fn test_py_slice_full() {
184        Python::with_gil(|py| {
185            let slice = PySlice::full_bound(py);
186            assert!(slice.getattr("start").unwrap().is_none(),);
187            assert!(slice.getattr("stop").unwrap().is_none(),);
188            assert!(slice.getattr("step").unwrap().is_none(),);
189            assert_eq!(
190                slice.indices(0).unwrap(),
191                PySliceIndices {
192                    start: 0,
193                    stop: 0,
194                    step: 1,
195                    slicelength: 0,
196                },
197            );
198            assert_eq!(
199                slice.indices(42).unwrap(),
200                PySliceIndices {
201                    start: 0,
202                    stop: 42,
203                    step: 1,
204                    slicelength: 42,
205                },
206            );
207        });
208    }
209
210    #[test]
211    fn test_py_slice_indices_new() {
212        let start = 0;
213        let stop = 0;
214        let step = 0;
215        assert_eq!(
216            PySliceIndices::new(start, stop, step),
217            PySliceIndices {
218                start,
219                stop,
220                step,
221                slicelength: 0
222            }
223        );
224
225        let start = 0;
226        let stop = 100;
227        let step = 10;
228        assert_eq!(
229            PySliceIndices::new(start, stop, step),
230            PySliceIndices {
231                start,
232                stop,
233                step,
234                slicelength: 0
235            }
236        );
237
238        let start = 0;
239        let stop = -10;
240        let step = -1;
241        assert_eq!(
242            PySliceIndices::new(start, stop, step),
243            PySliceIndices {
244                start,
245                stop,
246                step,
247                slicelength: 0
248            }
249        );
250
251        let start = 0;
252        let stop = -10;
253        let step = 20;
254        assert_eq!(
255            PySliceIndices::new(start, stop, step),
256            PySliceIndices {
257                start,
258                stop,
259                step,
260                slicelength: 0
261            }
262        );
263    }
264}