Search code examples
pythonoopoverridingself

Why using self keyword when calling parent method from child method in Python?


Why it's required to use self keyword as an argument when calling the parent method from the child method?

Let me give an example,


class Account:

     def __init__(self,filepath):
         self.filepath = filepath
         with open(self.filepath,"r") as file:
              self.blanace = int(file.read())

     def withDraw(self,amount):
          self.blanace = self.blanace - amount
          self.commit()

     def deposite(self,amount):
          self.blanace = self.blanace + amount
          self.commit()


     def commit(self):
          with open(self.filepath,"w") as file:
               file.write(str(self.blanace))



class Checking(Account):

      def __init__(self,filepath):
           Account.__init__(sellf,filepath) ######## I'm asking about this line.

Regarding this code,

I understand that self is automatically passed to the class when declaring a new object, so,

I expect when I declare new object, python will set self = the declared object, so now the self keyword will be available in the "'init'" child method, so no need to write it manually again like

Account.__init__(sellf,filepath) ######## I'm asking about this line.

Solution

  • All instance methods are just function-valued class attributes. If you access the attribute via an instance, some behind-the-scenes "magic" (known as the descriptor protocol) takes care of changing foo.bar() to type(foo).bar(foo). __init__ itself is also just another instance method, albeit one you usually only call explicitly when overriding __init__ in a child.

    In your example, you are explicitly invoking the parent class's __init__ method via the class, so you have to pass self explicitly (self.__init__(filepath) would result in infinite recursion).

    One way to avoid this is to not refer to the parent class explicitly, but to let a proxy determine the "closest" parent for you.

     super().__init__(filepath)
    

    There is some magic here: super with no arguments determines, with some help from the Python implementation, which class it statically occurs in (in this case, Checking) and passes that, along with self, as the implicit arguments to super. In Python 2, you always had to be explicit: super(Checking, self).__init__(filepath). (In Python 3, you can still pass argument explicitly, because there are some use cases, though rare, for passing arguments other than the current static class and self. Most commonly, super(SomeClass) does not get self as an implicit second argument, and handles class-level proxying.)


    Specifically, the function class defines a __get__ method; if the result of an attribute lookup defines __get__, the return value of that method is returned instead of the attribute value itself. In other words,

    foo.bar
    

    becomes

    foo.__dict__['bar'].__get__(foo, type(foo))
    

    and that return value is an object of type method. Calling a method instance simply causes the original function to be called, with its first argument being the instance that __get__ took as its first argument, and its remaining arguments are whatever other arguments were passed to the original method call.