pyo3/
pyclass.rs

1//! `PyClass` and related traits.
2use crate::{
3    callback::IntoPyCallbackOutput, ffi, impl_::pyclass::PyClassImpl, IntoPy, PyObject, PyResult,
4    PyTypeInfo, Python,
5};
6use std::{cmp::Ordering, os::raw::c_int};
7
8mod create_type_object;
9mod gc;
10
11pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
12
13pub use self::gc::{PyTraverseError, PyVisit};
14
15/// Types that can be used as Python classes.
16///
17/// The `#[pyclass]` attribute implements this trait for your Rust struct -
18/// you shouldn't implement this trait directly.
19#[allow(deprecated)]
20#[cfg(feature = "gil-refs")]
21pub trait PyClass: PyTypeInfo<AsRefTarget = crate::PyCell<Self>> + PyClassImpl {
22    /// Whether the pyclass is frozen.
23    ///
24    /// This can be enabled via `#[pyclass(frozen)]`.
25    type Frozen: Frozen;
26}
27
28/// Types that can be used as Python classes.
29///
30/// The `#[pyclass]` attribute implements this trait for your Rust struct -
31/// you shouldn't implement this trait directly.
32#[cfg(not(feature = "gil-refs"))]
33pub trait PyClass: PyTypeInfo + PyClassImpl {
34    /// Whether the pyclass is frozen.
35    ///
36    /// This can be enabled via `#[pyclass(frozen)]`.
37    type Frozen: Frozen;
38}
39
40/// Operators for the `__richcmp__` method
41#[derive(Debug, Clone, Copy)]
42pub enum CompareOp {
43    /// The *less than* operator.
44    Lt = ffi::Py_LT as isize,
45    /// The *less than or equal to* operator.
46    Le = ffi::Py_LE as isize,
47    /// The equality operator.
48    Eq = ffi::Py_EQ as isize,
49    /// The *not equal to* operator.
50    Ne = ffi::Py_NE as isize,
51    /// The *greater than* operator.
52    Gt = ffi::Py_GT as isize,
53    /// The *greater than or equal to* operator.
54    Ge = ffi::Py_GE as isize,
55}
56
57impl CompareOp {
58    /// Conversion from the C enum.
59    pub fn from_raw(op: c_int) -> Option<Self> {
60        match op {
61            ffi::Py_LT => Some(CompareOp::Lt),
62            ffi::Py_LE => Some(CompareOp::Le),
63            ffi::Py_EQ => Some(CompareOp::Eq),
64            ffi::Py_NE => Some(CompareOp::Ne),
65            ffi::Py_GT => Some(CompareOp::Gt),
66            ffi::Py_GE => Some(CompareOp::Ge),
67            _ => None,
68        }
69    }
70
71    /// Returns if a Rust [`std::cmp::Ordering`] matches this ordering query.
72    ///
73    /// Usage example:
74    ///
75    /// ```rust
76    /// # use pyo3::prelude::*;
77    /// # use pyo3::class::basic::CompareOp;
78    ///
79    /// #[pyclass]
80    /// struct Size {
81    ///     size: usize,
82    /// }
83    ///
84    /// #[pymethods]
85    /// impl Size {
86    ///     fn __richcmp__(&self, other: &Size, op: CompareOp) -> bool {
87    ///         op.matches(self.size.cmp(&other.size))
88    ///     }
89    /// }
90    /// ```
91    pub fn matches(&self, result: Ordering) -> bool {
92        match self {
93            CompareOp::Eq => result == Ordering::Equal,
94            CompareOp::Ne => result != Ordering::Equal,
95            CompareOp::Lt => result == Ordering::Less,
96            CompareOp::Le => result != Ordering::Greater,
97            CompareOp::Gt => result == Ordering::Greater,
98            CompareOp::Ge => result != Ordering::Less,
99        }
100    }
101}
102
103/// Output of `__next__` which can either `yield` the next value in the iteration, or
104/// `return` a value to raise `StopIteration` in Python.
105///
106/// Usage example:
107///
108/// ```rust
109/// # #![allow(deprecated)]
110/// use pyo3::prelude::*;
111/// use pyo3::iter::IterNextOutput;
112///
113/// #[pyclass]
114/// struct PyClassIter {
115///     count: usize,
116/// }
117///
118/// #[pymethods]
119/// impl PyClassIter {
120///     #[new]
121///     pub fn new() -> Self {
122///         PyClassIter { count: 0 }
123///     }
124///
125///     fn __next__(&mut self) -> IterNextOutput<usize, &'static str> {
126///         if self.count < 5 {
127///             self.count += 1;
128///             // Given an instance `counter`, First five `next(counter)` calls yield 1, 2, 3, 4, 5.
129///             IterNextOutput::Yield(self.count)
130///         } else {
131///             // At the sixth time, we get a `StopIteration` with `'Ended'`.
132///             //     try:
133///             //         next(counter)
134///             //     except StopIteration as e:
135///             //         assert e.value == 'Ended'
136///             IterNextOutput::Return("Ended")
137///         }
138///     }
139/// }
140/// ```
141#[deprecated(since = "0.21.0", note = "Use `Option` or `PyStopIteration` instead.")]
142pub enum IterNextOutput<T, U> {
143    /// The value yielded by the iterator.
144    Yield(T),
145    /// The `StopIteration` object.
146    Return(U),
147}
148
149/// Alias of `IterNextOutput` with `PyObject` yield & return values.
150#[deprecated(since = "0.21.0", note = "Use `Option` or `PyStopIteration` instead.")]
151#[allow(deprecated)]
152pub type PyIterNextOutput = IterNextOutput<PyObject, PyObject>;
153
154#[allow(deprecated)]
155impl<T, U> IntoPyCallbackOutput<*mut ffi::PyObject> for IterNextOutput<T, U>
156where
157    T: IntoPy<PyObject>,
158    U: IntoPy<PyObject>,
159{
160    fn convert(self, py: Python<'_>) -> PyResult<*mut ffi::PyObject> {
161        match self {
162            IterNextOutput::Yield(o) => Ok(o.into_py(py).into_ptr()),
163            IterNextOutput::Return(o) => {
164                Err(crate::exceptions::PyStopIteration::new_err(o.into_py(py)))
165            }
166        }
167    }
168}
169
170/// Output of `__anext__`.
171///
172/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>
173#[deprecated(
174    since = "0.21.0",
175    note = "Use `Option` or `PyStopAsyncIteration` instead."
176)]
177pub enum IterANextOutput<T, U> {
178    /// An expression which the generator yielded.
179    Yield(T),
180    /// A `StopAsyncIteration` object.
181    Return(U),
182}
183
184/// An [IterANextOutput] of Python objects.
185#[deprecated(
186    since = "0.21.0",
187    note = "Use `Option` or `PyStopAsyncIteration` instead."
188)]
189#[allow(deprecated)]
190pub type PyIterANextOutput = IterANextOutput<PyObject, PyObject>;
191
192#[allow(deprecated)]
193impl<T, U> IntoPyCallbackOutput<*mut ffi::PyObject> for IterANextOutput<T, U>
194where
195    T: IntoPy<PyObject>,
196    U: IntoPy<PyObject>,
197{
198    fn convert(self, py: Python<'_>) -> PyResult<*mut ffi::PyObject> {
199        match self {
200            IterANextOutput::Yield(o) => Ok(o.into_py(py).into_ptr()),
201            IterANextOutput::Return(o) => Err(crate::exceptions::PyStopAsyncIteration::new_err(
202                o.into_py(py),
203            )),
204        }
205    }
206}
207
208/// A workaround for [associated const equality](https://github.com/rust-lang/rust/issues/92827).
209///
210/// This serves to have True / False values in the [`PyClass`] trait's `Frozen` type.
211#[doc(hidden)]
212pub mod boolean_struct {
213    pub(crate) mod private {
214        use super::*;
215
216        /// A way to "seal" the boolean traits.
217        pub trait Boolean {
218            const VALUE: bool;
219        }
220
221        impl Boolean for True {
222            const VALUE: bool = true;
223        }
224        impl Boolean for False {
225            const VALUE: bool = false;
226        }
227    }
228
229    pub struct True(());
230    pub struct False(());
231}
232
233/// A trait which is used to describe whether a `#[pyclass]` is frozen.
234#[doc(hidden)]
235pub trait Frozen: boolean_struct::private::Boolean {}
236
237impl Frozen for boolean_struct::True {}
238impl Frozen for boolean_struct::False {}
239
240mod tests {
241    #[test]
242    fn test_compare_op_matches() {
243        use super::CompareOp;
244        use std::cmp::Ordering;
245
246        assert!(CompareOp::Eq.matches(Ordering::Equal));
247        assert!(CompareOp::Ne.matches(Ordering::Less));
248        assert!(CompareOp::Ge.matches(Ordering::Greater));
249        assert!(CompareOp::Gt.matches(Ordering::Greater));
250        assert!(CompareOp::Le.matches(Ordering::Equal));
251        assert!(CompareOp::Lt.matches(Ordering::Less));
252    }
253}