Skip to main content

pyo3/types/
range.rs

1use crate::sealed::Sealed;
2use crate::types::PyAnyMethods;
3use crate::{ffi, Bound, PyAny, PyResult, PyTypeInfo, Python};
4#[cfg(RustPython)]
5use crate::{
6    sync::PyOnceLock,
7    types::{PyType, PyTypeMethods},
8    Py,
9};
10
11/// Represents a Python `range`.
12///
13/// Values of this type are accessed via PyO3's smart pointers, e.g. as
14/// [`Py<PyRange>`][crate::Py] or [`Bound<'py, PyRange>`][Bound].
15///
16/// For APIs available on `range` objects, see the [`PyRangeMethods`] trait which is implemented for
17/// [`Bound<'py, PyRange>`][Bound].
18#[repr(transparent)]
19pub struct PyRange(PyAny);
20
21#[cfg(not(RustPython))]
22pyobject_native_type_core!(PyRange, pyobject_native_static_type_object!(ffi::PyRange_Type), "builtins", "range", #checkfunction=ffi::PyRange_Check);
23
24#[cfg(RustPython)]
25pyobject_native_type_core!(
26    PyRange,
27    |py| {
28        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
29        TYPE.import(py, "builtins", "range").unwrap().as_type_ptr()
30    },
31    "builtins",
32    "range",
33    #checkfunction=ffi::PyRange_Check
34);
35
36impl<'py> PyRange {
37    /// Creates a new Python `range` object with a default step of 1.
38    pub fn new(py: Python<'py>, start: isize, stop: isize) -> PyResult<Bound<'py, Self>> {
39        Self::new_with_step(py, start, stop, 1)
40    }
41
42    /// Creates a new Python `range` object with a specified step.
43    pub fn new_with_step(
44        py: Python<'py>,
45        start: isize,
46        stop: isize,
47        step: isize,
48    ) -> PyResult<Bound<'py, Self>> {
49        unsafe {
50            Ok(Self::type_object(py)
51                .call1((start, stop, step))?
52                .cast_into_unchecked())
53        }
54    }
55}
56
57/// Implementation of functionality for [`PyRange`].
58///
59/// These methods are defined for the `Bound<'py, PyRange>` smart pointer, so to use method call
60/// syntax these methods are separated into a trait, because stable Rust does not yet support
61/// `arbitrary_self_types`.
62#[doc(alias = "PyRange")]
63pub trait PyRangeMethods<'py>: Sealed {
64    /// Returns the start of the range.
65    fn start(&self) -> PyResult<isize>;
66
67    /// Returns the exclusive end of the range.
68    fn stop(&self) -> PyResult<isize>;
69
70    /// Returns the step of the range.
71    fn step(&self) -> PyResult<isize>;
72}
73
74impl<'py> PyRangeMethods<'py> for Bound<'py, PyRange> {
75    fn start(&self) -> PyResult<isize> {
76        self.getattr(intern!(self.py(), "start"))?.extract()
77    }
78
79    fn stop(&self) -> PyResult<isize> {
80        self.getattr(intern!(self.py(), "stop"))?.extract()
81    }
82
83    fn step(&self) -> PyResult<isize> {
84        self.getattr(intern!(self.py(), "step"))?.extract()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_py_range_new() {
94        Python::attach(|py| {
95            let range = PyRange::new(py, isize::MIN, isize::MAX).unwrap();
96            assert_eq!(range.start().unwrap(), isize::MIN);
97            assert_eq!(range.stop().unwrap(), isize::MAX);
98            assert_eq!(range.step().unwrap(), 1);
99        });
100    }
101
102    #[test]
103    fn test_py_range_new_with_step() {
104        Python::attach(|py| {
105            let range = PyRange::new_with_step(py, 1, 10, 2).unwrap();
106            assert_eq!(range.start().unwrap(), 1);
107            assert_eq!(range.stop().unwrap(), 10);
108            assert_eq!(range.step().unwrap(), 2);
109        });
110    }
111}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here