pythonclasspython-object

Difference between calling __init__() of build-in class and __init__() of user-defined class


I am learning python classes and could not understand below behavior:

In below example, I am extending built-in str class:

class uStr(str):
    def __init__(self,u_str):
        str.__init__(u_str)       #<<<------- no self is needed

    def underline(self):
        underlines = format('', '-^' + str(len(self)))
        return str.__str__(self) + '\n' + underlines

In below example, I am extending a user-defined class Fraction:

from fraction import *

class MixedFraction(Fraction):
    def __init__(self, *args):
        Fraction.__init__(self, args[0], args[1])     #<<<-------self is needed

    def getWholeNum(self):
        return self.getNumerator() // self.getDenominator()

Why do we need to give self as argument in 2nd example in calling __init__ of super class, while there is no need to give self in calling str's __init__.


Solution

  • First, you don't need those __init__ implementations at all. You could just inherit the superclass implementations. If you actually need to customize construction, then for uStr, you should do it in __new__ instead of __init__.


    str doesn't have its own __init__. It does all its initialization in __new__, because str instances are immutable and initializing in __init__ would be mutative. str inherits __init__ from object.

    object.__init__ and object.__new__ are a little weird. If exactly one of object.__init__ or object.__new__ are overridden, then the overridden one throws an error if given arguments (beyond self) and the other ignores arguments, to save you the work of having to provide a do-nothing override. However, if both or neither are overridden, then both will throw an error if given arguments (beyond self). You can see this explained in a big comment in the source code.

    str implements __new__, but inherits __init__ from object. When you override __init__ and call str.__init__, you're really calling object.__init__.

    When you call str.__init__(u_str), you're actually making an object.__init__ call for u_str, the wrong object, rather than for self. Since u_str doesn't have an __init__ override (and since you're only passing one argument, which gets interpreted as self), object.__init__ silently does nothing.

    When you call str.__init__(self, u_str), you're making an object.__init__ call for self, but since self has both __new__ and __init__ overridden, object.__init__ complains about the u_str argument.

    It doesn't look like you actually need to override construction at all in your subclasses. If you do, the correct way to customize construction of a str subclass is to override __new__. If for some strange reason you had to call str.__init__, the correct call would be str.__init__(self) or super().__init__(). Since object.__init__ doesn't do any initialization, you could also leave out the superclass constructor call.