#![cfg(feature = "num-complex")]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-complex\"] }")]
use crate::{
ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Borrowed, Bound, FromPyObject, PyAny, PyErr,
Python,
};
use num_complex::Complex;
use std::ffi::c_double;
impl PyComplex {
pub fn from_complex_bound<F: Into<c_double>>(
py: Python<'_>,
complex: Complex<F>,
) -> Bound<'_, PyComplex> {
unsafe {
ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into())
.assume_owned(py)
.cast_into_unchecked()
}
}
}
macro_rules! complex_conversion {
($float: ty) => {
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl<'py> crate::conversion::IntoPyObject<'py> for Complex<$float> {
type Target = PyComplex;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
unsafe {
Ok(
ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double)
.assume_owned(py)
.cast_into_unchecked(),
)
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl<'py> crate::conversion::IntoPyObject<'py> for &Complex<$float> {
type Target = PyComplex;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl FromPyObject<'_, '_> for Complex<$float> {
type Error = PyErr;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Complex<$float>, Self::Error> {
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
unsafe {
let val = ffi::PyComplex_AsCComplex(obj.as_ptr());
if val.real == -1.0 {
if let Some(err) = PyErr::take(obj.py()) {
return Err(err);
}
}
Ok(Complex::new(val.real as $float, val.imag as $float))
}
#[cfg(any(Py_LIMITED_API, PyPy))]
unsafe {
use $crate::types::any::PyAnyMethods;
let complex;
let obj = if obj.is_instance_of::<PyComplex>() {
obj
} else if let Some(method) =
obj.lookup_special(crate::intern!(obj.py(), "__complex__"))?
{
complex = method.call0()?;
complex.as_borrowed()
} else {
obj
};
let ptr = obj.as_ptr();
let real = ffi::PyComplex_RealAsDouble(ptr);
if real == -1.0 {
if let Some(err) = PyErr::take(obj.py()) {
return Err(err);
}
}
let imag = ffi::PyComplex_ImagAsDouble(ptr);
Ok(Complex::new(real as $float, imag as $float))
}
}
}
};
}
complex_conversion!(f32);
complex_conversion!(f64);
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::generate_unique_module_name;
use crate::types::PyAnyMethods as _;
use crate::types::{complex::PyComplexMethods, PyModule};
use crate::IntoPyObject;
#[test]
fn from_complex() {
Python::attach(|py| {
let complex = Complex::new(3.0, 1.2);
let py_c = PyComplex::from_complex_bound(py, complex);
assert_eq!(py_c.real(), 3.0);
assert_eq!(py_c.imag(), 1.2);
});
}
#[test]
fn to_from_complex() {
Python::attach(|py| {
let val = Complex::new(3.0f64, 1.2);
let obj = val.into_pyobject(py).unwrap();
assert_eq!(obj.extract::<Complex<f64>>().unwrap(), val);
});
}
#[test]
fn from_complex_err() {
Python::attach(|py| {
let obj = vec![1i32].into_pyobject(py).unwrap();
assert!(obj.extract::<Complex<f64>>().is_err());
});
}
#[test]
fn from_python_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class A:
def __complex__(self): return 3.0+1.2j
class B:
def __float__(self): return 3.0
class C:
def __index__(self): return 3
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let from_complex = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
from_complex.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
let from_float = module.getattr("B").unwrap().call0().unwrap();
assert_eq!(
from_float.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
#[cfg(Py_3_8)]
{
let from_index = module.getattr("C").unwrap().call0().unwrap();
assert_eq!(
from_index.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
}
})
}
#[test]
fn from_python_inherited_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class First: pass
class ComplexMixin:
def __complex__(self): return 3.0+1.2j
class FloatMixin:
def __float__(self): return 3.0
class IndexMixin:
def __index__(self): return 3
class A(First, ComplexMixin): pass
class B(First, FloatMixin): pass
class C(First, IndexMixin): pass
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let from_complex = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
from_complex.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
let from_float = module.getattr("B").unwrap().call0().unwrap();
assert_eq!(
from_float.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
#[cfg(Py_3_8)]
{
let from_index = module.getattr("C").unwrap().call0().unwrap();
assert_eq!(
from_index.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
}
})
}
#[test]
fn from_python_noncallable_descriptor_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class A:
@property
def __complex__(self):
return lambda: 3.0+1.2j
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let obj = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
obj.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
})
}
#[test]
fn from_python_nondescriptor_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class MyComplex:
def __call__(self): return 3.0+1.2j
class A:
__complex__ = MyComplex()
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let obj = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
obj.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
})
}
}