1use super::{PyStr, PyType, PyTypeRef, float};
2use crate::{
3 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4 builtins::PyUtf8StrRef,
5 class::{PyClassDef, PyClassImpl},
6 common::{format::FormatSpec, wtf8::Wtf8Buf},
7 convert::{IntoPyException, ToPyObject, ToPyResult},
8 function::{FuncArgs, OptionalArg, PyComparisonValue},
9 protocol::PyNumberMethods,
10 stdlib::_warnings,
11 types::{AsNumber, Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
12};
13use core::cell::Cell;
14use core::num::Wrapping;
15use core::ptr::NonNull;
16use num_complex::Complex64;
17use num_traits::Zero;
18use rustpython_common::hash;
19
20#[pyclass(module = false, name = "complex")]
21#[derive(Debug, Copy, Clone, PartialEq)]
22pub struct PyComplex {
23 #[pymember(name = "real", path = "re")]
24 #[pymember(name = "imag", path = "im")]
25 value: Complex64,
26}
27
28impl Py<PyComplex> {
29 #[must_use]
30 #[inline]
31 pub const fn as_complex(&self) -> Complex64 {
32 self.payload.to_complex()
33 }
34}
35
36thread_local! {
38 static COMPLEX_FREELIST: Cell<crate::object::FreeList<PyComplex>> = const { Cell::new(crate::object::FreeList::new()) };
39}
40
41impl PyPayload for PyComplex {
42 const MAX_FREELIST: usize = 100;
43 const HAS_FREELIST: bool = true;
44
45 #[inline]
46 fn class(ctx: &Context) -> &'static Py<PyType> {
47 ctx.types.complex_type
48 }
49
50 #[inline]
51 unsafe fn freelist_push(obj: *mut PyObject) -> bool {
52 COMPLEX_FREELIST
53 .try_with(|fl| {
54 let mut list = fl.take();
55 let stored = if list.len() < Self::MAX_FREELIST {
56 list.push(obj);
57 true
58 } else {
59 false
60 };
61 fl.set(list);
62 stored
63 })
64 .unwrap_or(false)
65 }
66
67 #[inline]
68 unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
69 COMPLEX_FREELIST
70 .try_with(|fl| {
71 let mut list = fl.take();
72 let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
73 fl.set(list);
74 result
75 })
76 .ok()
77 .flatten()
78 }
79}
80
81impl ToPyObject for Complex64 {
82 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
83 PyComplex::from(self).to_pyobject(vm)
84 }
85}
86
87impl From<Complex64> for PyComplex {
88 fn from(value: Complex64) -> Self {
89 Self { value }
90 }
91}
92
93impl PyObjectRef {
94 pub fn try_complex(&self, vm: &VirtualMachine) -> PyResult<Option<(Complex64, bool)>> {
97 if let Some(complex) = self.downcast_ref_if_exact::<PyComplex>(vm) {
98 return Ok(Some((complex.as_complex(), true)));
99 }
100 if let Some(method) = vm.get_method(self.clone(), identifier!(vm, __complex__)) {
101 let result = method?.call((), vm)?;
102
103 let ret_class = result.class().to_owned();
104 if let Some(ret) = result.downcast_ref::<PyComplex>()
105 && !result.class().is(vm.ctx.types.complex_type)
106 {
107 _warnings::warn(
108 vm.ctx.exceptions.deprecation_warning,
109 format!(
110 "__complex__ returned non-complex (type {ret_class}). \
111 The ability to return an instance of a strict subclass of complex \
112 is deprecated, and may be removed in a future version of Python."
113 ),
114 1,
115 vm,
116 )?;
117
118 return Ok(Some((ret.as_complex(), true)));
119 }
120
121 return match result.downcast_ref::<PyComplex>() {
122 Some(complex_obj) => Ok(Some((complex_obj.as_complex(), true))),
123 None => Err(vm.new_type_error(format!(
124 "__complex__ returned non-complex (type '{}')",
125 result.class().name()
126 ))),
127 };
128 }
129 if let Some(complex) = self.downcast_ref::<PyComplex>() {
132 return Ok(Some((complex.as_complex(), true)));
133 }
134
135 if let Some(float) = self.try_float_opt(vm) {
136 return Ok(Some((Complex64::new(float?.to_f64(), 0.0), false)));
137 }
138
139 Ok(None)
140 }
141}
142
143pub(crate) fn init(context: &'static Context) {
144 PyComplex::extend_class(context, context.types.complex_type);
145}
146
147fn to_op_complex(value: &PyObject, vm: &VirtualMachine) -> PyResult<Option<Complex64>> {
148 let r = if let Some(complex) = value.downcast_ref::<PyComplex>() {
149 Some(complex.as_complex())
150 } else {
151 float::to_op_float(value, vm)?.map(|float| Complex64::new(float, 0.0))
152 };
153 Ok(r)
154}
155
156const ONE: Complex64 = Complex64::new(1.0, 0.0);
157
158fn signed_unit(part: f64) -> f64 {
162 if part.is_infinite() { 1.0 } else { 0.0f64 }.copysign(part)
163}
164
165fn tamed(value: Complex64) -> Complex64 {
166 Complex64::new(signed_unit(value.re), signed_unit(value.im))
167}
168
169fn nans_to_zero(value: Complex64) -> Complex64 {
170 let zeroed = |part: f64| {
171 if part.is_nan() {
172 0.0f64.copysign(part)
173 } else {
174 part
175 }
176 };
177 Complex64::new(zeroed(value.re), zeroed(value.im))
178}
179
180fn prod(a: Complex64, b: Complex64) -> Complex64 {
183 let r = a * b;
184 if !(r.re.is_nan() && r.im.is_nan()) {
185 return r;
186 }
187
188 let (mut a, mut b) = (a, b);
191 let a_infinite = a.re.is_infinite() || a.im.is_infinite();
192 if a_infinite {
193 a = tamed(a);
194 b = nans_to_zero(b);
195 }
196 let b_infinite = b.re.is_infinite() || b.im.is_infinite();
197 if b_infinite {
198 b = tamed(b);
199 a = nans_to_zero(a);
200 }
201 let overflowed = !a_infinite
203 && !b_infinite
204 && ((a.re * b.re).is_infinite()
205 || (a.im * b.im).is_infinite()
206 || (a.re * b.im).is_infinite()
207 || (a.im * b.re).is_infinite());
208 if overflowed {
209 a = nans_to_zero(a);
210 b = nans_to_zero(b);
211 }
212
213 if !(a_infinite || b_infinite || overflowed) {
214 return r;
215 }
216 Complex64::new(
217 f64::INFINITY * (a.re * b.re - a.im * b.im),
218 f64::INFINITY * (a.re * b.im + a.im * b.re),
219 )
220}
221
222fn rc_quot(a: f64, b: Complex64) -> Option<Complex64> {
225 let abs_re = b.re.abs();
226 let abs_im = b.im.abs();
227
228 let r = if abs_re >= abs_im {
229 if abs_re == 0.0 {
230 return None;
231 }
232 let ratio = b.im / b.re;
233 let denom = b.re + b.im * ratio;
234 Complex64::new(a / denom, -a * ratio / denom)
235 } else if abs_im >= abs_re {
236 let ratio = b.re / b.im;
237 let denom = b.re * ratio + b.im;
238 Complex64::new(a * ratio / denom, -a / denom)
239 } else {
240 Complex64::new(f64::NAN, f64::NAN)
242 };
243
244 if r.re.is_nan() && r.im.is_nan() && (b.re.is_infinite() || b.im.is_infinite()) && a.is_finite()
247 {
248 let Complex64 { re: x, im: y } = tamed(b);
249 return Some(Complex64::new(0.0 * (a * x), 0.0 * -(a * y)));
250 }
251
252 Some(r)
253}
254
255fn quot(a: Complex64, b: Complex64) -> Option<Complex64> {
259 let abs_re = b.re.abs();
260 let abs_im = b.im.abs();
261
262 let r = if abs_re >= abs_im {
263 if abs_re == 0.0 {
264 return None;
265 }
266 let ratio = b.im / b.re;
267 let denom = b.re + b.im * ratio;
268 Complex64::new((a.re + a.im * ratio) / denom, (a.im - a.re * ratio) / denom)
269 } else if abs_im >= abs_re {
270 let ratio = b.re / b.im;
271 let denom = b.re * ratio + b.im;
272 Complex64::new((a.re * ratio + a.im) / denom, (a.im * ratio - a.re) / denom)
273 } else {
274 Complex64::new(f64::NAN, f64::NAN)
276 };
277
278 Some(recovered_quot(r, a, b))
279}
280
281fn recovered_quot(r: Complex64, a: Complex64, b: Complex64) -> Complex64 {
284 if !(r.re.is_nan() && r.im.is_nan()) {
285 return r;
286 }
287
288 if (a.re.is_infinite() || a.im.is_infinite()) && b.re.is_finite() && b.im.is_finite() {
289 let Complex64 { re: x, im: y } = tamed(a);
290 Complex64::new(
291 f64::INFINITY * (x * b.re + y * b.im),
292 f64::INFINITY * (y * b.re - x * b.im),
293 )
294 } else if (b.re.is_infinite() || b.im.is_infinite()) && a.re.is_finite() && a.im.is_finite() {
295 let Complex64 { re: x, im: y } = tamed(b);
296 Complex64::new(0.0 * (a.re * x + a.im * y), 0.0 * (a.im * x - a.re * y))
297 } else {
298 r
299 }
300}
301
302fn inner_div(v1: Complex64, v2: Complex64, vm: &VirtualMachine) -> PyResult<Complex64> {
303 quot(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
304}
305
306fn pow_unsigned(x: Complex64, n: u32) -> Complex64 {
308 let mut r = ONE;
309 let mut p = x;
310 let mut mask = 1u32;
311 while mask > 0 && n >= mask {
312 if n & mask != 0 {
313 r = prod(r, p);
314 }
315 mask <<= 1;
316 p = prod(p, p);
317 }
318 r
319}
320
321fn powi(x: Complex64, n: i32) -> Option<Complex64> {
323 if n > 0 {
324 Some(pow_unsigned(x, n as u32))
325 } else {
326 quot(ONE, pow_unsigned(x, n.unsigned_abs()))
327 }
328}
329
330pub(crate) fn complex_pow(
331 v1: Complex64,
332 v2: Complex64,
333 vm: &VirtualMachine,
334) -> PyResult<Complex64> {
335 let exponent = v2.re as i32;
338 let result = if v2.im == 0.0 && v2.re == f64::from(exponent) && v2.re.abs() <= 100.0 {
339 powi(v1, exponent)
340 } else if v1.is_zero() && (v2.im != 0.0 || v2.re < 0.0) {
341 None
342 } else {
343 Some(powc(v1, v2))
344 };
345
346 let Some(result) = result else {
347 return Err(vm.new_zero_division_error("zero to a negative or complex power"));
348 };
349 if result.re.is_infinite() || result.im.is_infinite() {
350 return Err(vm.new_overflow_error("complex exponentiation"));
351 }
352 Ok(result)
353}
354
355fn powc(a: Complex64, exp: Complex64) -> Complex64 {
357 if exp.is_zero() {
358 return ONE;
359 }
360 if a.is_zero() {
361 return Complex64::new(0.0, 0.0);
362 }
363
364 let magnitude = a.norm();
365 let angle = a.arg();
366 let mut len = magnitude.powf(exp.re);
367 let mut phase = angle * exp.re;
368 if exp.im != 0.0 {
371 len *= (-angle * exp.im).exp();
372 phase += exp.im * magnitude.ln();
373 }
374 Complex64::new(len * phase.cos(), len * phase.sin())
375}
376
377fn has_misplaced_underscore(bytes: &[u8]) -> bool {
380 let mut prev = b'\0';
381 for &byte in bytes {
382 if byte == b'_' {
383 if !prev.is_ascii_digit() {
384 return true;
385 }
386 } else if prev == b'_' && !byte.is_ascii_digit() {
387 return true;
388 }
389 prev = byte;
390 }
391 prev == b'_'
392}
393
394impl Constructor for PyComplex {
395 type Args = ComplexArgs;
396
397 fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
398 if cls.is(vm.ctx.types.complex_type)
400 && func_args.args.len() == 1
401 && func_args.kwargs.is_empty()
402 && func_args.args[0].class().is(vm.ctx.types.complex_type)
403 {
404 return Ok(func_args.args[0].clone());
405 }
406
407 let args: Self::Args = func_args.bind_for(vm, Self::NAME)?;
408 let payload = Self::py_new(&cls, args, vm)?;
409 payload.into_ref_with_type(vm, cls).map(Into::into)
410 }
411
412 fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
413 let imag_missing = args.imag.is_missing();
414 let (real, real_was_complex) = match args.real {
415 OptionalArg::Missing => (Complex64::new(0.0, 0.0), false),
416 OptionalArg::Present(val) => {
417 if let Some(c) = val.try_complex(vm)? {
418 c
419 } else if let Some(s) = val.downcast_ref::<PyStr>() {
420 if args.imag.is_present() {
421 return Err(vm.new_type_error(
422 "complex() can't take second arg if first is a string",
423 ));
424 }
425 if has_misplaced_underscore(s.as_wtf8().as_bytes()) {
426 let repr = val.repr(vm)?;
427 return Err(vm.new_value_error(format!(
428 "could not convert string to complex: {repr}"
429 )));
430 }
431 let (re, im) = rustpython_literal::complex::parse_str(
432 &crate::protocol::numeric_literal_from_str(s),
433 )
434 .ok_or_else(|| vm.new_value_error("complex() arg is a malformed string"))?;
435 return Ok(Self::from(Complex64 { re, im }));
436 } else {
437 return Err(vm.new_type_error(format!(
438 "complex() argument must be a string or a number, not {}",
439 val.class().slot_name()
440 )));
441 }
442 }
443 };
444
445 let (imag, imag_was_complex) = match args.imag {
446 OptionalArg::Missing => (Complex64::new(real.im, 0.0), false),
449 OptionalArg::Present(obj) => {
450 if let Some(c) = obj.try_complex(vm)? {
451 c
452 } else if obj.class().fast_issubclass(vm.ctx.types.str_type) {
453 return Err(vm.new_type_error("complex() second arg can't be a string"));
454 } else {
455 return Err(vm.new_type_error(format!(
456 "complex() second argument must be a number, not '{}'",
457 obj.class().name()
458 )));
459 }
460 }
461 };
462
463 let final_real = if imag_was_complex {
464 real.re - imag.im
465 } else {
466 real.re
467 };
468
469 let final_imag = if real_was_complex && !imag_missing {
470 imag.re + real.im
471 } else {
472 imag.re
473 };
474 let value = Complex64::new(final_real, final_imag);
475 Ok(Self::from(value))
476 }
477}
478
479impl PyComplex {
480 #[deprecated(note = "use PyComplex::from(...).into_ref() instead")]
481 pub fn new_ref(value: Complex64, ctx: &Context) -> PyRef<Self> {
482 Self::from(value).into_ref(ctx)
483 }
484
485 #[must_use]
486 pub const fn to_complex64(self) -> Complex64 {
487 self.value
488 }
489
490 #[must_use]
491 pub const fn to_complex(&self) -> Complex64 {
492 self.value
493 }
494
495 fn number_op<F, R>(a: &PyObject, b: &PyObject, op: F, vm: &VirtualMachine) -> PyResult
496 where
497 F: FnOnce(Complex64, Complex64, &VirtualMachine) -> R,
498 R: ToPyResult,
499 {
500 if let (Some(a), Some(b)) = (to_op_complex(a, vm)?, to_op_complex(b, vm)?) {
501 op(a, b, vm).to_pyresult(vm)
502 } else {
503 Ok(vm.ctx.not_implemented())
504 }
505 }
506
507 fn complex_real_binop<CCF, RCF, CRF, R>(
508 a: &PyObject,
509 b: &PyObject,
510 cc_op: CCF,
511 cr_op: CRF,
512 rc_op: RCF,
513 vm: &VirtualMachine,
514 ) -> PyResult
515 where
516 CCF: FnOnce(Complex64, Complex64) -> R,
517 CRF: FnOnce(Complex64, f64) -> R,
518 RCF: FnOnce(f64, Complex64) -> R,
519 R: ToPyResult,
520 {
521 let value = match (a.downcast_ref::<Self>(), b.downcast_ref::<Self>()) {
522 (Some(a_complex), Some(b_complex)) => {
524 cc_op(a_complex.as_complex(), b_complex.as_complex())
525 }
526 (Some(a_complex), None) => {
527 let Some(b_real) = float::to_op_float(b, vm)? else {
528 return Ok(vm.ctx.not_implemented());
529 };
530
531 cr_op(a_complex.as_complex(), b_real)
533 }
534 (None, Some(b_complex)) => {
535 let Some(a_real) = float::to_op_float(a, vm)? else {
536 return Ok(vm.ctx.not_implemented());
537 };
538
539 rc_op(a_real, b_complex.as_complex())
541 }
542 (None, None) => return Ok(vm.ctx.not_implemented()),
543 };
544 value.to_pyresult(vm)
545 }
546}
547
548#[pyclass(
549 flags(BASETYPE),
550 with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
551)]
552impl Py<PyComplex> {
553 #[pymethod]
554 fn conjugate(&self) -> Complex64 {
555 self.value.conj()
556 }
557
558 #[pymethod]
559 fn __getnewargs__(&self) -> (f64, f64) {
560 let Complex64 { re, im } = self.value;
561 (re, im)
562 }
563
564 #[pymethod]
565 fn __format__(
566 zelf: &Self,
567 format_spec: PyUtf8StrRef,
568 vm: &VirtualMachine,
569 ) -> PyResult<Wtf8Buf> {
570 if format_spec.is_empty() {
572 return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
573 }
574 let format_spec =
575 FormatSpec::parse(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
576 let result = if format_spec.has_locale_format() {
577 let locale = crate::format::get_locale_info();
578 format_spec.format_complex_locale(&zelf.as_complex(), &locale)
579 } else {
580 format_spec.format_complex(&zelf.as_complex())
581 };
582 result
583 .map(Wtf8Buf::from_string)
584 .map_err(|err| err.into_pyexception(vm))
585 }
586
587 #[pyclassmethod]
588 fn from_number(cls: PyTypeRef, number: PyObjectRef, vm: &VirtualMachine) -> PyResult {
589 if number.class().is(vm.ctx.types.complex_type) && cls.is(vm.ctx.types.complex_type) {
590 return Ok(number);
591 }
592 let value = number
593 .try_complex(vm)?
594 .ok_or_else(|| {
595 vm.new_type_error(format!(
596 "must be real number, not {}",
597 number.class().name()
598 ))
599 })?
600 .0;
601 let result = vm.ctx.new_complex(value);
602 if cls.is(vm.ctx.types.complex_type) {
603 Ok(result.into())
604 } else {
605 PyType::call(&cls, vec![result.into()].into(), vm)
606 }
607 }
608}
609
610#[pyclass]
611impl PyRef<PyComplex> {
612 #[pymethod]
613 fn __complex__(self, vm: &VirtualMachine) -> Self {
614 if self.is(vm.ctx.types.complex_type) {
615 self
616 } else {
617 PyComplex::from(self.as_complex()).into_ref(&vm.ctx)
618 }
619 }
620}
621
622impl Comparable for PyComplex {
623 fn cmp(
624 zelf: &Py<Self>,
625 other: &PyObject,
626 op: PyComparisonOp,
627 vm: &VirtualMachine,
628 ) -> PyResult<PyComparisonValue> {
629 op.eq_only(|| {
630 let result = if let Some(other) = other.downcast_ref::<Self>() {
631 zelf.as_complex() == other.as_complex()
632 } else {
633 match float::to_op_float(other, vm) {
634 Ok(Some(other)) => zelf.as_complex() == other.into(),
635 Err(_) => false,
636 Ok(None) => return Ok(PyComparisonValue::NotImplemented),
637 }
638 };
639 Ok(PyComparisonValue::Implemented(result))
640 })
641 }
642}
643
644impl Hashable for PyComplex {
645 #[inline]
646 fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
647 let value = zelf.as_complex();
648
649 let re_hash =
650 hash::hash_float(value.re).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
651
652 let im_hash =
653 hash::hash_float(value.im).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
654
655 let Wrapping(ret) = Wrapping(re_hash) + Wrapping(im_hash) * Wrapping(hash::IMAG);
656 Ok(hash::fix_sentinel(ret))
657 }
658}
659
660impl AsNumber for PyComplex {
661 fn as_number() -> &'static PyNumberMethods {
662 static AS_NUMBER: PyNumberMethods = PyNumberMethods {
663 add: Some(|a, b, vm| {
664 PyComplex::complex_real_binop(
665 a,
666 b,
667 |a, b| a + b,
668 |a_complex, b_real| Complex64::new(a_complex.re + b_real, a_complex.im),
669 |a_real, b_complex| Complex64::new(a_real + b_complex.re, b_complex.im),
670 vm,
671 )
672 }),
673 subtract: Some(|a, b, vm| {
674 PyComplex::complex_real_binop(
675 a,
676 b,
677 |a, b| a - b,
678 |a_complex, b_real| Complex64::new(a_complex.re - b_real, a_complex.im),
679 |a_real, b_complex| Complex64::new(a_real - b_complex.re, -b_complex.im),
680 vm,
681 )
682 }),
683 multiply: Some(|a, b, vm| {
684 PyComplex::complex_real_binop(
685 a,
686 b,
687 prod,
688 |a_complex, b_real| {
689 Complex64::new(a_complex.re * b_real, a_complex.im * b_real)
690 },
691 |a_real, b_complex| {
692 Complex64::new(a_real * b_complex.re, a_real * b_complex.im)
693 },
694 vm,
695 )
696 }),
697 power: Some(|a, b, c, vm| {
698 if vm.is_none(c) {
699 PyComplex::number_op(a, b, complex_pow, vm)
700 } else {
701 Err(vm.new_value_error(String::from("complex modulo")))
702 }
703 }),
704 negative: Some(|number, vm| {
705 let value = PyComplex::number_downcast(number).as_complex();
706 (-value).to_pyresult(vm)
707 }),
708 positive: Some(|number, vm| {
709 PyComplex::number_downcast_exact(number, vm).to_pyresult(vm)
710 }),
711 absolute: Some(|number, vm| {
712 let value = PyComplex::number_downcast(number).as_complex();
713 let result = value.norm();
714 if result.is_infinite() && value.re.is_finite() && value.im.is_finite() {
716 return Err(vm.new_overflow_error("absolute value too large"));
717 }
718 result.to_pyresult(vm)
719 }),
720 boolean: Some(|number, _vm| {
721 Ok(!PyComplex::number_downcast(number).as_complex().is_zero())
722 }),
723 true_divide: Some(|a, b, vm| {
724 PyComplex::complex_real_binop(
725 a,
726 b,
727 |a, b| inner_div(a, b, vm),
728 |a_complex, b_real| {
729 if b_real == 0.0 {
730 Err(vm.new_zero_division_error("division by zero"))
731 } else {
732 Ok(Complex64::new(a_complex.re / b_real, a_complex.im / b_real))
733 }
734 },
735 |a_real, b_complex| {
736 rc_quot(a_real, b_complex)
737 .ok_or_else(|| vm.new_zero_division_error("division by zero"))
738 },
739 vm,
740 )
741 }),
742 ..PyNumberMethods::NOT_IMPLEMENTED
743 };
744 &AS_NUMBER
745 }
746
747 fn clone_exact(zelf: &Py<Self>, vm: &VirtualMachine) -> PyRef<Self> {
748 vm.ctx.new_complex(zelf.as_complex())
749 }
750}
751
752impl Representable for PyComplex {
753 #[inline]
754 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
755 let Complex64 { re, im } = zelf.as_complex();
758 Ok(rustpython_literal::complex::to_string(re, im))
759 }
760}
761
762#[derive(FromArgs)]
763pub struct ComplexArgs {
764 #[pyarg(any, default, py_default = "0")]
766 real: OptionalArg<PyObjectRef>,
767 #[pyarg(any, default, py_default = "0")]
769 imag: OptionalArg<PyObjectRef>,
770}