2.1.3 · D5OOP Fundamentals
Question bank — Instance attributes vs class attributes
True or false — justify
Every answer must name which bag (instance vs class) the read or write touched.
Assigning obj.x = 5 modifies the class attribute x.
False. A write always creates/updates a key in
obj.__dict__; the class's bag is never touched by obj.x = ....Reading obj.x when obj has no x of its own returns the class's x.
True. The instance bag misses, so lookup falls through to
type(obj).__dict__ and finds the shared value.After a.species = "Wolf", the value of Dog.species also becomes "Wolf".
False.
a.species = ... only added a shadowing key to a.__dict__; the class's single copy is untouched.Two instances always share a class attribute list, even after one of them does self.mylist = [].
False. Once an instance runs
self.mylist = [] (an assignment), it gets its own list in its bag and stops seeing the shared one.self.tricks.append("roll") creates a new list on the instance.
False.
.append is a read of self.tricks (finds the shared class list) then mutates it in place — no assignment, so no new instance list is born.In Python, a class attribute cannot be changed once the class is defined.
False.
ClassName.attr = new_value reassigns it freely; "class attribute" describes where it lives, not immutability.If you delete a shadowing instance attribute with del a.species, reading a.species errors out.
False. Deleting the instance copy just unshadows the class attribute; the read now falls through and returns the class value again.
Changing Dog.species after a has shadowed it will change what a.species returns.
False.
a reads its own copy first (step 1) and never reaches the class, so a is immune to the class-level change.An immutable class attribute like count = 0 is safe from the shared-mutable-default bug.
True. You can never mutate an int in place;
self.count += 1 is self.count = self.count + 1, an assignment that makes a per-instance copy.obj.x += 1, when x is only a class attribute, silently modifies the class value.
False.
+= expands to obj.x = obj.x + 1: it reads the class value but writes to the instance, creating a shadowing copy while the class stays put.Spot the error
Each snippet has one conceptual bug. Name it and the fix.
class Cart:
items = []
def add(self, x): self.items.append(x)What breaks and why?
items is a shared mutable class attribute; every cart appends to the same list. Fix: assign self.items = [] in __init__ so each cart gets a fresh list.class Player:
def __init__(self): self.hp = 100
def reset_all(self): Player.hp = 100Why is Player.hp = 100 wrong here?
hp is an instance attribute (born in __init__), so there is no class-level hp to reset; Player.hp = 100 just invents a new class attribute that most instances shadow. Reset per-object via self.hp = 100.class Config:
debug = False
c = Config()
c.debug = True # meant to flip it globallyWhy doesn't every config now see debug = True?
c.debug = True is a write — it lands on c.__dict__ and shadows only for c. To flip it for all, write on the class: Config.debug = True.class Counter:
n = 0
def bump(self): self.n += 1Why does bump fail to share a running total across instances?
self.n += 1 reads the class n but assigns back to self, creating a private instance n. Each object counts alone. Use Counter.n += 1 for a true shared total.class Dog:
legs = 4
d = Dog()
del d.legsWhy does del d.legs raise AttributeError?
d never had its own legs (it only read the class copy). You can only delete what lives in d.__dict__; the class attribute isn't deletable through the instance.Why questions
Explain the mechanism, not just the outcome.
Why is read = search up, write = always instance the single rule that explains everything?
Because shadowing, the mutable-default trap, and "why the class didn't change" are all just this asymmetry playing out — reads can fall through to shared data, writes never do.
Why does a shared mutable list bite you but a shared int doesn't?
You can mutate a list in place (
.append) without any assignment, so all instances share the change; an int can only change via reassignment, which is a write and forks a private copy.Why would putting per-object data on the class make objects "collide"?
There is only one shared copy, so every object reads and writes the same value — one dog's
name would overwrite every other dog's.Why does storing truly-shared data on every instance waste memory and complicate updates?
You'd hold N identical copies, and changing the shared fact would require editing all N instances instead of one class attribute.
Why does ClassName.attr = value affect only the instances that don't shadow it?
Shadowing instances find their own copy first and never reach the class; non-shadowing instances fall through and see the freshly updated class value live.
Why do we usually create instance attributes inside __init__ rather than in the class body?
__init__ runs once per object with self.x = ..., guaranteeing a fresh, private copy for each instance instead of one shared class-body value.Edge cases
Where the rule's corners live.
If a class and an instance both have an attribute named x, which wins on a read?
The instance wins — its bag is searched first (step 1), shadowing the class value.
What happens on obj.x if neither the instance, the class, nor any base class has x?
Lookup exhausts the whole chain and raises
AttributeError — there is no further place to search.A subclass defines its own class attribute with the same name as the parent's. Which does a subclass instance read?
The subclass's copy — the MRO searches the subclass before the parent, so the parent's value is shadowed at the class level.
You mutate Dog.tricks.append("x") directly on the class. Do existing instances see it?
Yes — any instance that never shadowed
tricks reads the same shared list object and sees the appended item.An instance shadows a class attribute, then you del the shadow. What does the next read return?
The class value again; deleting the instance copy simply lets the read fall through to the class.
Is self.count += 1 a read, a write, or both — and what does it create?
Both: it reads the current value (possibly from the class) then writes back to
self, creating a private instance count that shadows the class from then on.What does the class's __dict__ look like right after a.species = "Wolf" on an instance?
Unchanged — the new
"Wolf" entry lives in a.__dict__, and Dog.__dict__["species"] still holds the original value.Connections
- Instance attributes vs class attributes
- OOP Fundamentals
- Classes and objects
- The self parameter
- __init__ constructor
- Inheritance and MRO
- Namespaces and scope
- Mutable vs immutable objects