1 | use crate::err::{PyErr, PyResult}; |
2 | use crate::ffi; |
3 | use crate::ffi_ptr_ext::FfiPtrExt; |
4 | use crate::types::any::PyAnyMethods; |
5 | #[allow (deprecated)] |
6 | use crate::ToPyObject; |
7 | use crate::{Bound, IntoPyObject, PyAny, PyObject, Python}; |
8 | use 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)] |
20 | pub struct PySlice(PyAny); |
21 | |
22 | pyobject_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)] |
31 | pub 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 | |
46 | impl 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 | |
58 | impl 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" )] |
102 | pub 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 | |
109 | impl<'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)] |
140 | impl 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 | |
146 | impl<'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 | |
156 | impl<'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)] |
167 | mod 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 | } |
272 | |