2

I'm doing an optimisation on the complex plane, with an expensive function.

I'd like to have a class that's a complex number, which behaves like a complex number for arithmetic, but also has the result of the function, and a few convenient debug assists tacked onto it.

I threw the following together, and it was only later that I realised I didn't understand why it should be working, as I'd initially omitted the call to super().__init__(). I don't understand how the z argument is setting the .real and .imag parts of the number, or that it's not throwing an error at me. When I added the super(), it still worked exactly the same. However, super doesn't take an argument, so I don't see how that would magic the parameter z into .real and .imag either.

def spam(z):
    return abs(pow(z,3))

class Point(complex):
    def __init__(self, z):
        # super().__init__()    # doesn't seem to make any difference
        # super().__init__(z)   # throws an error

        try:
            Point.serial += 1
        except AttributeError:
            Point.serial = 0
        self.id = Point.serial

        self.y = spam(z)
        self.ge3 = self.y>=3       

    def __str__(self):
        return 'point {} ({}+{}j) = {} {}'.format(self.id,
                                                  self.real,
                                                  self.imag,
                                                  self.y,
                                                  ('below', 'above')[self.ge3])

p = Point(3)
q = Point(2j)
r = Point((p+q)/2)
print(p)
print(q)
print(r)

gives

point 0 (3.0+0.0j) = 27 above
point 1 (0.0+2.0j) = 8.0 above
point 2 (1.5+1.0j) = 5.859020822628983 above

Is this legal or expected behaviour? How is it working? Should I do it differently? Is it likely to stop working with future versions (3.5.2 presently)?

Neil_UK
  • 1,043
  • 12
  • 25

1 Answers1

2

Short answer

Your code is fine, the magic method __new__ inherited from complex is what actually creates your object, and it was actually called before the object initialization.

Calling super().__init__() would still be a better practice though.

Long answer

The method __init__ does not create your object, it only initializes it after creation. The method that creates it is called __new__. Have a look at the following code.

class Point(complex):
    def __init__(self, z):
        print(self)

x = Point(1+1j) # This prints (1+1j)

When __init__ is called, the object has already been created. But here is what happens if we were to override the __new__ method.

class Point(complex):
    def __new__(*args):
        return 0 # We purposedly return without calling super().__new__(*args)

    def __init__(self, z):
        print(self)

x = Point(1+1j) # x has value 0

x.imag # That fails

Here we indeed cannot call x.imag since complex.__new__ was never called. Notice that __init__ was not even called here, we will see why below.

In your code, the method complex.__new__ is called (namely with argument z) and returns a complex subclassed as a Point, which is then the object passed as self to __init__.

In general the flow is the following:

  1. MyType.__new__ is called when you do MyType(...) and it returns an object
  2. If the object returned by MyType.__new__ is an instance of MyType, then it is passed as self to MyType.__init__
  3. Otherwise, MyType.__init__ isn't called at all, this is what happened in the second example we saw

It is rare that you will actually need to override the __new__ method. Since you cannot really create an object there is little you can do other than call the type method or super().__new__ that cannot be done in __init__. It is mostly useful when it comes to metaclass, which are a lot of fun.

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
  • You'll notice my call to `super().__init__()` takes no parameters. I did first try it with a parameter z, and I got the error 'takes no parameters'. – Neil_UK Feb 08 '18 at 14:21
  • You are correct, I will update. I believe that in this case, super().__init__ does nothing at all so you can omit it. – Olivier Melançon Feb 08 '18 at 15:17