pyo3/conversions/
num_complex.rs1#![cfg(feature = "num-complex")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-complex\"] }")]
18#[cfg(feature = "experimental-inspect")]
97use crate::inspect::PyStaticExpr;
98#[cfg(feature = "experimental-inspect")]
99use crate::type_hint_identifier;
100use crate::{
101 ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Borrowed, Bound, FromPyObject, PyAny, PyErr,
102 Python,
103};
104use num_complex::Complex;
105use std::ffi::c_double;
106
107impl PyComplex {
108 pub fn from_complex_bound<F: Into<c_double>>(
110 py: Python<'_>,
111 complex: Complex<F>,
112 ) -> Bound<'_, PyComplex> {
113 unsafe {
114 ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into())
115 .assume_owned(py)
116 .cast_into_unchecked()
117 }
118 }
119}
120
121macro_rules! complex_conversion {
122 ($float: ty) => {
123 #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
124 impl<'py> crate::conversion::IntoPyObject<'py> for Complex<$float> {
125 type Target = PyComplex;
126 type Output = Bound<'py, Self::Target>;
127 type Error = std::convert::Infallible;
128
129 #[cfg(feature = "experimental-inspect")]
130 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("builtins", "complex");
131
132 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
133 unsafe {
134 Ok(
135 ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double)
136 .assume_owned(py)
137 .cast_into_unchecked(),
138 )
139 }
140 }
141 }
142
143 #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
144 impl<'py> crate::conversion::IntoPyObject<'py> for &Complex<$float> {
145 type Target = PyComplex;
146 type Output = Bound<'py, Self::Target>;
147 type Error = std::convert::Infallible;
148
149 #[cfg(feature = "experimental-inspect")]
150 const OUTPUT_TYPE: PyStaticExpr = <Complex<$float>>::OUTPUT_TYPE;
151
152 #[inline]
153 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
154 (*self).into_pyobject(py)
155 }
156 }
157
158 #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
159 impl FromPyObject<'_, '_> for Complex<$float> {
160 type Error = PyErr;
161
162 #[cfg(feature = "experimental-inspect")]
163 const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("builtins", "complex");
164
165 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Complex<$float>, Self::Error> {
166 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
167 unsafe {
168 let val = ffi::PyComplex_AsCComplex(obj.as_ptr());
169 if val.real == -1.0 {
170 if let Some(err) = PyErr::take(obj.py()) {
171 return Err(err);
172 }
173 }
174 Ok(Complex::new(val.real as $float, val.imag as $float))
175 }
176
177 #[cfg(any(Py_LIMITED_API, PyPy))]
178 unsafe {
179 use $crate::types::any::PyAnyMethods;
180 let complex;
181 let obj = if obj.is_instance_of::<PyComplex>() {
182 obj
183 } else if let Some(method) =
184 obj.lookup_special(crate::intern!(obj.py(), "__complex__"))?
185 {
186 complex = method.call0()?;
187 complex.as_borrowed()
188 } else {
189 obj
193 };
194 let ptr = obj.as_ptr();
195 let real = ffi::PyComplex_RealAsDouble(ptr);
196 if real == -1.0 {
197 if let Some(err) = PyErr::take(obj.py()) {
198 return Err(err);
199 }
200 }
201 let imag = ffi::PyComplex_ImagAsDouble(ptr);
202 Ok(Complex::new(real as $float, imag as $float))
203 }
204 }
205 }
206 };
207}
208complex_conversion!(f32);
209complex_conversion!(f64);
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::test_utils::generate_unique_module_name;
215 use crate::types::PyAnyMethods as _;
216 use crate::types::{complex::PyComplexMethods, PyModule};
217 use crate::IntoPyObject;
218
219 #[test]
220 fn from_complex() {
221 Python::attach(|py| {
222 let complex = Complex::new(3.0, 1.2);
223 let py_c = PyComplex::from_complex_bound(py, complex);
224 assert_eq!(py_c.real(), 3.0);
225 assert_eq!(py_c.imag(), 1.2);
226 });
227 }
228 #[test]
229 fn to_from_complex() {
230 Python::attach(|py| {
231 let val = Complex::new(3.0f64, 1.2);
232 let obj = val.into_pyobject(py).unwrap();
233 assert_eq!(obj.extract::<Complex<f64>>().unwrap(), val);
234 });
235 }
236 #[test]
237 fn from_complex_err() {
238 Python::attach(|py| {
239 let obj = vec![1i32].into_pyobject(py).unwrap();
240 assert!(obj.extract::<Complex<f64>>().is_err());
241 });
242 }
243 #[test]
244 fn from_python_magic() {
245 Python::attach(|py| {
246 let module = PyModule::from_code(
247 py,
248 cr#"
249class A:
250 def __complex__(self): return 3.0+1.2j
251class B:
252 def __float__(self): return 3.0
253class C:
254 def __index__(self): return 3
255 "#,
256 c"test.py",
257 &generate_unique_module_name("test"),
258 )
259 .unwrap();
260 let from_complex = module.getattr("A").unwrap().call0().unwrap();
261 assert_eq!(
262 from_complex.extract::<Complex<f64>>().unwrap(),
263 Complex::new(3.0, 1.2)
264 );
265 let from_float = module.getattr("B").unwrap().call0().unwrap();
266 assert_eq!(
267 from_float.extract::<Complex<f64>>().unwrap(),
268 Complex::new(3.0, 0.0)
269 );
270 let from_index = module.getattr("C").unwrap().call0().unwrap();
271 assert_eq!(
272 from_index.extract::<Complex<f64>>().unwrap(),
273 Complex::new(3.0, 0.0)
274 );
275 })
276 }
277 #[test]
278 fn from_python_inherited_magic() {
279 Python::attach(|py| {
280 let module = PyModule::from_code(
281 py,
282 cr#"
283class First: pass
284class ComplexMixin:
285 def __complex__(self): return 3.0+1.2j
286class FloatMixin:
287 def __float__(self): return 3.0
288class IndexMixin:
289 def __index__(self): return 3
290class A(First, ComplexMixin): pass
291class B(First, FloatMixin): pass
292class C(First, IndexMixin): pass
293 "#,
294 c"test.py",
295 &generate_unique_module_name("test"),
296 )
297 .unwrap();
298 let from_complex = module.getattr("A").unwrap().call0().unwrap();
299 assert_eq!(
300 from_complex.extract::<Complex<f64>>().unwrap(),
301 Complex::new(3.0, 1.2)
302 );
303 let from_float = module.getattr("B").unwrap().call0().unwrap();
304 assert_eq!(
305 from_float.extract::<Complex<f64>>().unwrap(),
306 Complex::new(3.0, 0.0)
307 );
308 let from_index = module.getattr("C").unwrap().call0().unwrap();
309 assert_eq!(
310 from_index.extract::<Complex<f64>>().unwrap(),
311 Complex::new(3.0, 0.0)
312 );
313 })
314 }
315 #[test]
316 fn from_python_noncallable_descriptor_magic() {
317 Python::attach(|py| {
321 let module = PyModule::from_code(
322 py,
323 cr#"
324class A:
325 @property
326 def __complex__(self):
327 return lambda: 3.0+1.2j
328 "#,
329 c"test.py",
330 &generate_unique_module_name("test"),
331 )
332 .unwrap();
333 let obj = module.getattr("A").unwrap().call0().unwrap();
334 assert_eq!(
335 obj.extract::<Complex<f64>>().unwrap(),
336 Complex::new(3.0, 1.2)
337 );
338 })
339 }
340 #[test]
341 fn from_python_nondescriptor_magic() {
342 Python::attach(|py| {
344 let module = PyModule::from_code(
345 py,
346 cr#"
347class MyComplex:
348 def __call__(self): return 3.0+1.2j
349class A:
350 __complex__ = MyComplex()
351 "#,
352 c"test.py",
353 &generate_unique_module_name("test"),
354 )
355 .unwrap();
356 let obj = module.getattr("A").unwrap().call0().unwrap();
357 assert_eq!(
358 obj.extract::<Complex<f64>>().unwrap(),
359 Complex::new(3.0, 1.2)
360 );
361 })
362 }
363}