Woody1193
Woody1193

Reputation: 8010

Why, when calling a function with self on the parent class, the child class is actually run in Python

I have two abstract classes with the following definition:

from abc import ABC, abstractmethod

class A(ABC):

    def __init__(self, lst):
        self.List = lst

    @abstractmethod
    def __enter__(self) -> 'Component':
        return self

    @abstractmethod
    def __exit__(self, *exc):
        pass

class B(ABC):

    def __init__(self, lst):
        self.List = lst

    @abstractmethod
    def run(self):
        pass

Now, I have a class that inherits from these:

class C(A, B):

    def __init__(self, lst):
        A.__init__(self, lst)
        B.__init__(self, lst)

    def __enter__(self) -> 'C':
        self.List.append('C.__enter__')
        self.run()
        return self

    def __exit__(self, *exc):
        self.List.append('C.__exit__')

    def run(self):
        self.List.append('C.run')

Finally, I have a class that inherits from C:

class D(C):

    def __init__(self, lst):
        super().__init__(lst)

   def __enter__(self) -> 'D':
       self.List.append('D.__enter__')
       super().__enter__()
       return self

   def __exit__(self, *exc):
       super().__exit__()
       self.List.append('D.__exit__')

   def run(self):
       self.List.append('D.run')
       super().run()

Now, my code looks like this:

my_d = D( ['start'] )
with my_d:
    pass
print(my_d)

From my understanding of how super() works this should produce the following:

[ start,
  D.__enter__,
  C.__enter__,
  C.run,
  C.__exit__,
  D.__exit__ ]

but what actually happens is:

[ start,
  D.__enter__,
  C.__enter__,
  D.run,
  C.run,
  C.__exit__,
  D.__exit__ ]

Nowhere do I explicitly call D.run and yet D.run is called.

This doesn't really make sense to me unless, when I call super().__enter__ in D, self somehow thinks it's still inside D when it's actually in C. Can anyone enlighten me on this?

Upvotes: 1

Views: 74

Answers (2)

Marcel Wilson
Marcel Wilson

Reputation: 4532

def run() in D overrides the def run() in C. So when you call run() in C.__enter__, it actually calls D.run().
When D.run() is called the super().run() calls C.run().

It was just as confusing to me the first time I learned about python class inheritance but that's just how it works.

Upvotes: 1

hiro protagonist
hiro protagonist

Reputation: 46921

you call C.run when you invoke D.run:

class  D(C):


def run(self):
    self.List.append('D.run')
    super().run()  # this will add 'C.run' to self.List

just the same as with __exit__ and __enter__.

D.run is called from the chain D.__enter__ -> C.__enter__ which now calls self.run() (and as self has type D this will call D.run -> C.run).

self does not think it is 'inside D'; self is of the type D.


if you want to have your desired output you could just not override run in D; that way only C.run would be called.

Upvotes: 3

Related Questions