
Ameer AbdullahI shared this code with 20 developers ranging from students to people with three or more years of...
I shared this code with 20 developers ranging from students to people with three or more years of experience. I asked them to write down the output before running it.
18 of them got it wrong.
class Config:
settings = {}
def add(self, key, value):
self.settings[key] = value
c1 = Config()
c2 = Config()
c1.add("theme", "dark")
c2.add("language", "Python")
print(c1.settings)
print(c2.settings)
Take a moment. What do you think both lines print?
The most common answer was:
{'theme': 'dark'}
{'language': 'Python'}
The logic: c1 and c2 are separate objects, so their settings should be separate.
{'theme': 'dark', 'language': 'Python'}
{'theme': 'dark', 'language': 'Python'}
Both instances share the same dictionary. Adding to c2 also affected c1.
settings = {} is a class variable. It is defined on the Config class itself, not on any instance.
When you write self.settings[key] = value, Python looks up self.settings. It checks the instance's __dict__ first. Finding no settings attribute there, it looks at the class. It finds Config.settings which is the shared dictionary.
It then calls dict.__setitem__ on that shared dictionary. This mutates the shared object. Both c1 and c2 read from the same dictionary so both see all additions.
class Config:
def __init__(self):
self.settings = {}
def add(self, key, value):
self.settings[key] = value
c1 = Config()
c2 = Config()
c1.add("theme", "dark")
c2.add("language", "Python")
print(c1.settings)
print(c2.settings)
Output:
{'theme': 'dark'}
{'language': 'Python'}
Moving settings = {} into __init__ creates a new dictionary for each instance. self.settings in __init__ creates an instance attribute that shadows any class attribute of the same name.
This question reveals whether a candidate understands the difference between class attributes and instance attributes at the level of Python's actual object model.
Writing a class is easy. Understanding how Python resolves attribute access through the instance's __dict__ first and then the class is the underlying knowledge being tested.
The follow-up question is always: "What if settings were a string instead of a dict? Would the behavior be different?"
class Config:
name = "default"
def set_name(self, new_name):
self.name = new_name
c1 = Config()
c2 = Config()
c1.set_name("custom")
print(c1.name)
print(c2.name)
Output:
custom
default
With a string, self.name = new_name creates a new instance attribute on c1 that shadows the class attribute. c2 has no instance attribute so it still reads the class attribute "default".
With a dict, self.settings[key] = value mutates the existing class attribute through the reference. It never creates an instance attribute.
Mutation versus rebinding. The same distinction that explains list and integer += behavior.
For more class tracing problems, try PyCodeIt.