Home > Software engineering >  Calling super().__init__(*args, **kwargs) in a fractions.Fraction subclass accepts only the instance
Calling super().__init__(*args, **kwargs) in a fractions.Fraction subclass accepts only the instance

Time:07-31

from fractions import Fraction


class F1(Fraction):
    def __init__(self, *args, **kwargs):
        Fraction.__init__(*args, **kwargs)

class F2(Fraction):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


Fraction(1, 10) # Fraction(1, 10)

F1(1, 10) # F1(1, 10)

F2(1, 10) # TypeError: object.__init__() takes exactly one argument (the instance to initialize)

How does this happen? Could someone elaborate a bit on the super function?

Python version: 3.8.10

CodePudding user response:

TLDR; Fraction uses __new__ instead of __init__.

Python objects are assembled in two steps. First, they are created with __new__ and then they are initialized with __init__. Usually we use the default object.__new__ to create a new empty object and then override __init__ for anything special that needs to be done.

But some classes want to control the object creation step by implementing their own __new__. This is especially the case for immutable classes like Fraction. Fraction.__new__ returns an object of its own super class and when that happens, python skips calling __init__ completely. In the case of Fraction, its __init__ is really just object.__init__ which only accepts the single self parameter and just returns without doing anything. It is never meant to be called.

When you implemented F1.__init__, you had a bug and this bug masks the problem. When you call a superclass method directly, you need to put the self parameter in the call. You should have done Fraction.__init__(self, *args, **kwargs). Had you done so, you'd get the same error as in the F2 case (because super().__init__(*args, **kwargs) does add the self).

But really you have a more pressing problem. Its okay to have your own __init__, but with restrictions. You can't initialize the Fraction because that was done in __new__ before __init__ was called. And you can't call Fraction.__init__ which does nothing except explode when given parameters. You could add other attributes to the object, but that's about it. But other strange things will happen. Unless you override methods like __add__, they will return objects of the original Fraction type because that's the __new__ that is being called. When your parent class uses __new__, you really want to override that __new__.

  • Related