2

I am trying to write a class that simulates a complex number in Python. Yes, I know there already exists a built-in function called complex() which can be used to construct a complex number, and we can also use j to create them. However I wanted to write my own just for fun. Here is a small version of it:

class Complex:

    def __init__(self, real: float = 0, img: float = 0):
        self.real = real
        self.img = img
        self.num = (self.real, self.img)

    def __repr__(self) -> str:
        string = "{real}".format(real=self.real) if self.real != 0 else ""
        # Want to have "a + bi", "a", "bi"
        # Want to avoid "+ bi" or "a + "
        if self.real != 0 and self.img < 0:
            string += " - "  # img will already have -
        elif self.real != 0 and self.img > 0:
            string += " + "
        string += "{img}i".format(img=abs(self.img)) if self.img != 0 else ""
        return string

    def __add__(self, other: 'Complex') -> 'Complex':
        return Complex(self.real + other.real, self.img + other.img)

    def __sub__(self, other: 'Complex') -> 'Complex':
        return Complex(self.real - other.real, self.img - other.img)

    def __matmul__(self, other: 'Complex') -> 'Complex':
        # (ac - bd)
        real = self.real * other.real - self.img * other.img
        # (ad + bc)
        img = self.real * other.img + self.img * other.real
        return Complex(real, img)

    def __mul__(self, other):
        return Complex(self.real * other, self.img * other)

    def __rmul__(self, other):
        return self.__mul__(other)

The issue is that I cannot find a way of defining the following:

  • Complex * Complex (left multiplication for class instances)
  • Complex * Complex (right multiplication for class instances, but useless as the above will suffice)
  • float * Complex (right scalar multiplication, but can be with int as well)
  • Complex * float (left scalar multiplication, but can be with int as well)

This is because when I multiply two class instances, it still calls __mul__ while it does not call __matmul__. I think __matmul__ should only work with @ operator. How can I make all of this work without having to use another operator (@) ? I would like to use the standard * operator.

9
  • Why did you implement __matmul__? Commented Jul 25, 2018 at 16:53
  • Because I thought that would have worked with instance * instance as well for some reason. The thing is I don't want to check whether "other" in __mul__ is of type Complex or of type int or float Commented Jul 25, 2018 at 16:55
  • 1
    "The thing is I don't want to check whether "other" in __mul__ is of type Complex or of type int or float" - just check already. It's the standard approach, and essentially mandatory. Commented Jul 25, 2018 at 16:57
  • 2
    You shouldn't even have those type hints - methods like __add__ or __mul__ are supposed to take arbitrary types and return NotImplemented if they don't recognize how to handle the argument. Commented Jul 25, 2018 at 17:00
  • 1
    Your __repr__ should be __str__; the string returned by __repr__ should, to the extent possible, return a string that could be used to recreate the instance. In this case, something like return "Complex({}, {})".format(self.real, self.imag). Commented Jul 25, 2018 at 17:12

1 Answer 1

2

In this case, you have to test for type and execute the proper arithmetic based on type.

class Complex:

    def __init__(self, real: float = 0, img: float = 0):
        self.real = real
        self.img = img
        self.num = (self.real, self.img)

    def __repr__(self) -> str:
        string = "{real}".format(real=self.real) if self.real != 0 else ""
        # Want to have "a + bi", "a", "bi"
        # Want to avoid "+ bi" or "a + "
        if self.real != 0 and self.img < 0:
            string += " - "  # img will already have -
        elif self.real != 0 and self.img > 0:
            string += " + "
        string += "{img}i".format(img=abs(self.img)) if self.img != 0 else ""
        return string

    def __add__(self, other):
        if isinstance(other, Complex):
            return Complex(self.real + other.real, self.img + other.img)
        elif isinstance(other, int) or isinstance(other, float):
            return Complex(self.real + other, self.img)
        
    def __radd__(self, other):
        return self + other

    def __sub__(self, other):
        if isinstance(other, Complex):
            return Complex(self.real - other.real, self.img - other.img)
        elif isinstance(other, int) or isinstance(other, float):
            return Complex(self.real - other, self.img)
        
    def __rsub__(self, other):
        return -self + other
    
    def __neg__(self):
        return Complex(-self.real, -self.img)
    
    def __mul__(self, other):
        if isinstance(other, Complex):
            real = self.real * other.real - self.img * other.img   # (ac - bd)
            img = self.real * other.img + self.img * other.real    # (ad + bc)
            return Complex(real, img)
        elif isinstance(other, int) or isinstance(other, float):
            return Complex(self.real * other, self.img * other)

    def __rmul__(self, other):
        return self * other
    

and a few tests:

a, b = Complex(1, 2), Complex(2, 1)
a + 2, 2 + a, a - 2, 2 - a, 2 * a, a * 2, a + b, b + a, a - b, b - a, a * b, b * a

output:

(3 + 2i,
 3 + 2i,
 -1 + 2i,
 1 - 2i,
 2 + 4i,
 2 + 4i,
 3 + 3i,
 3 + 3i,
 -1 + 1i,
 1 - 1i,
 5i,
 5i)

In the same vein, you can implement __iadd__ (+=), __isub__ (-=), __imul__ (*=), paying attention to mutate the argument instead of returning a new object.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.