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