The full complexity of Python's OOP implementation is far beyond the scope of a Stack Overflow answer, but it's possible to provide an overview. This will gloss over a lot of details, like metaclasses, multiple inheritance, descriptors, and the C-level API. Still, it should give you a sense of why implementing such a thing is possible, as well as a general impression of how it's done. If you want the full details, you should browse the CPython source code.
An object like an instance of your Person class consists of the following things:
- A class
- A dict holding its attributes
- Other stuff that's not relevant right now, like
__weakref__.
A class is also pretty simple. It has
- A base class
- A dict holding its attributes
- Other stuff that's not relevant right now, like the class name.
When you define a class with a class statement, Python bundles up a pointer to the base class you picked (or object if you didn't pick one) and a dict holding the methods you defined, and that's your new class object. It's kind of like the following tuple
Person = (object,
{'__init__': <that __init__ method you wrote>,
'getInfo': <that getInfo method you wrote>},
those irrelevant bits we're glossing over)
but not a tuple. (At C level, this record is almost, but not quite, implemented as a struct.)
When you create an instance of your class, Python bundles together a pointer to your class and a new dict for the instance's attributes, and that's your instance. It's kind of like the following tuple:
personOne = (Person, {}, those irrelevant bits we're glossing over)
but again, not a tuple. Again, it's almost, but not quite, implemented as a struct at C level.
Then it runs __init__, passing __init__ the new instance and whatever other arguments you provided to your class:
Person.__init__(personOne, "Bob", 34)
Attribute assignment is translated to setting entries in the object's dict, so the assignments in __init__:
def __init__(self, name, age):
self.name = name
self.age = age
cause the dict to end up in the following state:
{'name': 'Bob', 'age': 34}
When you call personOne.getInfo(), Python looks in personOne's dict, then its class's dict, then its superclass's dict, etc., until it finds an entry for the 'getInfo' key. The associated value will be the getInfo method. If the method was found in a class dict, Python will insert personOne as the first argument. (The details of how it knows to insert that argument are in the descriptor protocol.)