Based on what I was seeing here, I did some experiments to try to understand better what is going on:
class BaseClass: def __init__(self): self.a = 1 def base_method(self): return self.a def another_base_method(self): return self.a + 1 class SubClass(BaseClass): def __init__(self): super().__init__() self.b = 2 c = SubClass() print(c.__dict__) print(c.__class__.__dict__) print(c.__class__.__subclasses__()) print(c.__class__.mro()) print(c.__class__.mro()[1].__dict__) print(getattr(c, "base_method")) print(c.b) print(c.a) With some notes: print(c.__dict__) {'a': 1, 'b': 2} So the instance directly has a. I am guessing that the object's own dictionary is directly getting these are both __init__'s are run. print(c.__class__.__dict__) {'__module__': '__main__', '__init__': <function SubClass.__init__ at 0x000001BEEF46E488>, '__doc__': None} I am guessing this is what is found and stuffed into the class' namespace when the class is built; that's specifically the BUILD_CLASS opcode doing its thing. print(c.__class__.__subclasses__()) [] What?! Why isn't this [<class '__main__.BaseClass'>]? print(c.__class__.mro()) [<class '__main__.SubClass'>, <class '__main__.BaseClass'>, <class 'object'>] This is more like what I expected to find with subclasses. Okay, no, method resolution order is showing the entire order. print(c.__class__.mro()[1].__dict__) {'__module__': '__main__', '__init__': <function BaseClass.__init__ at 0x000001BEEF46E2F0>, 'base_method': <function BaseClass.base_method at 0x000001BEEF46E378>, 'another_base_method': <function BaseClass.another_base_method at 0x000001BEEF46E400>, '__dict__': <attribute '__dict__' of 'BaseClass' objects>, '__weakref__': <attribute '__weakref__' of 'BaseClass' objects>, '__doc__': None} No instance-level stuff. Looks like it's the base class namespace when the BUILD_CLASS opcode saw it. Okay, looking good. print(getattr(c, "base_method")) <bound method BaseClass.base_method of <__main__.SubClass object at 0x000001BEEF2D9898>> I'm guessing here it didn't find it in the object's __dict__ nor the class' __dict__ so it went in mro and found it in BaseClass. So I need a __dict__ for the class based on the code defined for it when the class is defined. That's associated with the class. I need another dictionary for each instance. That will get stuffed with whatever started getting dumped into it in __init__ (and possibly elsewhere afterwards). What __dict__ actually is can vary. The mappingproxy helps make sure that strings are given as keys (among other things?). -- https://mail.python.org/mailman/listinfo/python-list