Puzzle 1: Explanation
Explore Python's attribute lookup process by understanding how instance and class dictionaries interact. Learn how Python handles attribute retrieval and assignment, including the effects of modifying attributes at the instance level versus the class level.
We'll cover the following...
Let’s try it!
Try executing the code below to verify the results:
Code explanation
When we write self.count, we’re doing an attribute lookup. The attribute that we’re looking for in this case is count. Getting an attribute in Python is a complex operation. Almost every Python object stores its attributes in a dict called __dict__.
Python will first try to find the attribute in the instance dictionary, then in the instance’s class dictionary __class__, and then up the inheritance hierarchy __mro__.
Finally, if the attribute we’re looking for is not found, Python will raise an AttributeError.
Tip: Python’s attribute lookup is quite complex. Some objects such as C extensions and classes with slots don’t have a dict. There are also descriptors, the getattribute special methods, and other special cases.
Here’s some possible code for the algorithm, which is implemented in Python using the built-in getattr method:
def get_attr(obj, name):
"""Emulate built in getattr"""
if name in obj.__dict__:
print(f'found {name} in obj')
return obj.__dict__[name]
if name in obj.__class__.__dict__:
print(f'found {name} in class')
return obj.__class__.__dict__[name]
for cls in obj.__class__.__mro__:
if name in cls.__dict__:
print(f'found {name} in {cls.__name__}')
return cls.__dict__[name]
raise AttributeError(name)
What happens when we try self.count += 1?
If we try self.count += 1 in the teaser, Python will translate it to self.count = self.count + 1. This means it’ll use getattr(self, count) and get the count defined in the Player class with the value of 0.
Once Python has the value of self.count + 1 = 1 on the right-hand side of the assignment (=), it’ll call setattr(self, count, 1). The setattr function will create a new entry in self.__dict__ that will shadow the count in Player. After that, we can print the Player.count, which is still 0. If we print p1.count, we’ll get 1.