In Python, inheritance means sharing namespaces. All objects share namespaces with their parent class, and all classes in the method resolution order (MRO) of that class. "Method" resolution order is a bit of a misnomer, it' just attribute resolution order (all methods are attributes, not all attributes are methods).
But in this simple case, an instances shares the state of its class and the state of the parent class, in other words, lookups check the class namespace and the parent class namespace if no instance attribute is found (instance attributes being precisely those that belong to the instance namespace).
The lookups occur in that order. And assignments happen directly to the object that is being assigned to, be it the instance, the class, or some other class in the method resolution order.
Names "lower down" the namespace ladder [I]shadow[/I] the same name in higher namespaces.
The other thing to understand here is that @classmethod provides the type (i.e. class) of the instance of class it is called on.
When you do:
@classmethod
def change_game_title(cls, new_title):
cls.game_title = new_title
cls gets the class of the instance you've called it upon.
So for the player instance,
cls.game_title = new_title
Means:
Player.game_title = new_title
And for game instances, that means:
Game.game_title = new_title
So when you look up player.game_title, it first checks the instance namespace, doesn't find it, and then it's parent's namespace, doesn't find it, and finally, the grandparent.
Here are the namespaces, retrieved usings vars*:
In [3]: vars(game), *map(vars, type(game).mro())
Out[3]:
({'game_name': 'Game One'},
mappingproxy({'__module__': '__main__',
'game_title': 'FIRST',
'__init__': <function __main__.Game.__init__(self)>,
'change_game_name': <function __main__.Game.change_game_name(self, new_name)>,
'change_game_title': <classmethod at 0x7fa5d4cde4d0>,
'__dict__': <attribute '__dict__' of 'Game' objects>,
'__weakref__': <attribute '__weakref__' of 'Game' objects>,
'__doc__': None}),
mappingproxy({'__repr__': <slot wrapper '__repr__' of 'object' objects>,
'__hash__': <slot wrapper '__hash__' of 'object' objects>,
'__str__': <slot wrapper '__str__' of 'object' objects>,
'__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>,
'__setattr__': <slot wrapper '__setattr__' of 'object' objects>,
'__delattr__': <slot wrapper '__delattr__' of 'object' objects>,
'__lt__': <slot wrapper '__lt__' of 'object' objects>,
'__le__': <slot wrapper '__le__' of 'object' objects>,
'__eq__': <slot wrapper '__eq__' of 'object' objects>,
'__ne__': <slot wrapper '__ne__' of 'object' objects>,
'__gt__': <slot wrapper '__gt__' of 'object' objects>,
'__ge__': <slot wrapper '__ge__' of 'object' objects>,
'__init__': <slot wrapper '__init__' of 'object' objects>,
'__new__': <function object.__new__(*args, **kwargs)>,
'__reduce_ex__': <method '__reduce_ex__' of 'object' objects>,
'__reduce__': <method '__reduce__' of 'object' objects>,
'__subclasshook__': <method '__subclasshook__' of 'object' objects>,
'__init_subclass__': <method '__init_subclass__' of 'object' objects>,
'__format__': <method '__format__' of 'object' objects>,
'__sizeof__': <method '__sizeof__' of 'object' objects>,
'__dir__': <method '__dir__' of 'object' objects>,
'__class__': <attribute '__class__' of 'object' objects>,
'__doc__': 'The most base type'}))
In [4]: vars(player), *map(vars, type(player).mro())
Out[4]:
({'game_name': 'Game One', 'player_name': 'Bob'},
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.Player.__init__(self)>,
'__doc__': None}),
mappingproxy({'__module__': '__main__',
'game_title': 'FIRST',
'__init__': <function __main__.Game.__init__(self)>,
'change_game_name': <function __main__.Game.change_game_name(self, new_name)>,
'change_game_title': <classmethod at 0x7fa5d4cde4d0>,
'__dict__': <attribute '__dict__' of 'Game' objects>,
'__weakref__': <attribute '__weakref__' of 'Game' objects>,
'__doc__': None}),
mappingproxy({'__repr__': <slot wrapper '__repr__' of 'object' objects>,
'__hash__': <slot wrapper '__hash__' of 'object' objects>,
'__str__': <slot wrapper '__str__' of 'object' objects>,
'__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>,
'__setattr__': <slot wrapper '__setattr__' of 'object' objects>,
'__delattr__': <slot wrapper '__delattr__' of 'object' objects>,
'__lt__': <slot wrapper '__lt__' of 'object' objects>,
'__le__': <slot wrapper '__le__' of 'object' objects>,
'__eq__': <slot wrapper '__eq__' of 'object' objects>,
'__ne__': <slot wrapper '__ne__' of 'object' objects>,
'__gt__': <slot wrapper '__gt__' of 'object' objects>,
'__ge__': <slot wrapper '__ge__' of 'object' objects>,
'__init__': <slot wrapper '__init__' of 'object' objects>,
'__new__': <function object.__new__(*args, **kwargs)>,
'__reduce_ex__': <method '__reduce_ex__' of 'object' objects>,
'__reduce__': <method '__reduce__' of 'object' objects>,
'__subclasshook__': <method '__subclasshook__' of 'object' objects>,
'__init_subclass__': <method '__init_subclass__' of 'object' objects>,
'__format__': <method '__format__' of 'object' objects>,
'__sizeof__': <method '__sizeof__' of 'object' objects>,
'__dir__': <method '__dir__' of 'object' objects>,
'__class__': <attribute '__class__' of 'object' objects>,
'__doc__': 'The most base type'}))
Here's what happens after mutator methods are run, that set the attribute using the dynamically resolved cls, which is basically just type(instance).
In [5]: game.change_game_title("SECOND")
...:
In [6]: player.change_game_title("THIRD")
...:
In [7]: vars(game), *map(vars, type(game).mro())
Out[7]:
({'game_name': 'Game One'},
mappingproxy({'__module__': '__main__',
'game_title': 'SECOND',
'__init__': <function __main__.Game.__init__(self)>,
'change_game_name': <function __main__.Game.change_game_name(self, new_name)>,
'change_game_title': <classmethod at 0x7fa5d4cde4d0>,
'__dict__': <attribute '__dict__' of 'Game' objects>,
'__weakref__': <attribute '__weakref__' of 'Game' objects>,
'__doc__': None}),
mappingproxy({'__repr__': <slot wrapper '__repr__' of 'object' objects>,
'__hash__': <slot wrapper '__hash__' of 'object' objects>,
'__str__': <slot wrapper '__str__' of 'object' objects>,
'__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>,
'__setattr__': <slot wrapper '__setattr__' of 'object' objects>,
'__delattr__': <slot wrapper '__delattr__' of 'object' objects>,
'__lt__': <slot wrapper '__lt__' of 'object' objects>,
'__le__': <slot wrapper '__le__' of 'object' objects>,
'__eq__': <slot wrapper '__eq__' of 'object' objects>,
'__ne__': <slot wrapper '__ne__' of 'object' objects>,
'__gt__': <slot wrapper '__gt__' of 'object' objects>,
'__ge__': <slot wrapper '__ge__' of 'object' objects>,
'__init__': <slot wrapper '__init__' of 'object' objects>,
'__new__': <function object.__new__(*args, **kwargs)>,
'__reduce_ex__': <method '__reduce_ex__' of 'object' objects>,
'__reduce__': <method '__reduce__' of 'object' objects>,
'__subclasshook__': <method '__subclasshook__' of 'object' objects>,
'__init_subclass__': <method '__init_subclass__' of 'object' objects>,
'__format__': <method '__format__' of 'object' objects>,
'__sizeof__': <method '__sizeof__' of 'object' objects>,
'__dir__': <method '__dir__' of 'object' objects>,
'__class__': <attribute '__class__' of 'object' objects>,
'__doc__': 'The most base type'}))
In [8]: vars(player), *map(vars, type(player).mro())
Out[8]:
({'game_name': 'Game One', 'player_name': 'Bob'},
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.Player.__init__(self)>,
'__doc__': None,
'game_title': 'THIRD'}),
mappingproxy({'__module__': '__main__',
'game_title': 'SECOND',
'__init__': <function __main__.Game.__init__(self)>,
'change_game_name': <function __main__.Game.change_game_name(self, new_name)>,
'change_game_title': <classmethod at 0x7fa5d4cde4d0>,
'__dict__': <attribute '__dict__' of 'Game' objects>,
'__weakref__': <attribute '__weakref__' of 'Game' objects>,
'__doc__': None}),
mappingproxy({'__repr__': <slot wrapper '__repr__' of 'object' objects>,
'__hash__': <slot wrapper '__hash__' of 'object' objects>,
'__str__': <slot wrapper '__str__' of 'object' objects>,
'__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>,
'__setattr__': <slot wrapper '__setattr__' of 'object' objects>,
'__delattr__': <slot wrapper '__delattr__' of 'object' objects>,
'__lt__': <slot wrapper '__lt__' of 'object' objects>,
'__le__': <slot wrapper '__le__' of 'object' objects>,
'__eq__': <slot wrapper '__eq__' of 'object' objects>,
'__ne__': <slot wrapper '__ne__' of 'object' objects>,
'__gt__': <slot wrapper '__gt__' of 'object' objects>,
'__ge__': <slot wrapper '__ge__' of 'object' objects>,
'__init__': <slot wrapper '__init__' of 'object' objects>,
'__new__': <function object.__new__(*args, **kwargs)>,
'__reduce_ex__': <method '__reduce_ex__' of 'object' objects>,
'__reduce__': <method '__reduce__' of 'object' objects>,
'__subclasshook__': <method '__subclasshook__' of 'object' objects>,
'__init_subclass__': <method '__init_subclass__' of 'object' objects>,
'__format__': <method '__format__' of 'object' objects>,
'__sizeof__': <method '__sizeof__' of 'object' objects>,
'__dir__': <method '__dir__' of 'object' objects>,
'__class__': <attribute '__class__' of 'object' objects>,
'__doc__': 'The most base type'}))