2.1.3 · D3OOP Fundamentals

Worked examples — Instance attributes vs class attributes

2,723 words12 min readBack to topic

The scenario matrix

Think of every attribute-access as choosing three switches:

  1. Where do we touch it? — on the instance (obj.) or on the class (ClassName.)
  2. Read or write? — reading (print(obj.x), obj.x.append(...)) or writing (obj.x = ...)
  3. What kind of value?immutable (int, str, tuple) or mutable (list, dict, set)

Every bug and every "wait, why?" moment is just one box in this table:

# Touch Action Value kind What happens Cell name
C1 instance read any falls through to class if not shadowed fall-through read
C2 instance write immutable creates instance copy → shadows class shadow write
C3 class write any changes the one shared copy for all non-shadowers class rewrite
C4 instance read+mutate mutable mutates the shared class object (no copy!) mutable trap
C5 instance write then mutate mutable own copy → safe, isolated the fix
C6 instance delete (del) any removes shadow → class value reappears un-shadow
C7 class read via __dict__ any proves where the value physically lives namespace proof
C8 zero/degenerate empty class, no instance attrs, hasattr on nothing degenerate
C9 inheritance read any child instance falls through to parent class MRO fall-through
C10 counter idiom write on class immutable a class attribute used as a global tally word problem

Below: one worked example per cell. Guess the output before reading the steps.


C1 · Fall-through read

Figure — Instance attributes vs class attributes

C2 · Shadow write (immutable)

Figure — Instance attributes vs class attributes

C3 · Class rewrite


C4 · The mutable trap

Figure — Instance attributes vs class attributes

C5 · The fix (per-instance mutable)


C6 · Un-shadow with del


C7 · Namespace proof


C8 · Degenerate / zero cases


C9 · MRO fall-through (inheritance)


C10 · Word problem — a shared counter


Recall One-line summary of the whole matrix

Every cell reduces to two facts. What are the two facts that generate every case? ::: READ searches instance → class → bases; WRITE (including +=, del) always targets the instance unless you name the class explicitly.


Active recall

In C1, why is a.species is b.species True?
Both reads fall through to the one shared class copy, so both names point at the same object.
In C2, after a.species = "Wolf", what is in b.__dict__?
Nothing named speciesb still falls through to the untouched class value.
In C4, what is a.tricks is Dog.tricks?
True — the append only read+mutated the single shared class list; no instance copy was made.
In C6, what does a second del a.species raise?
AttributeError, because del only removes from the instance and the shadow is already gone.
In C8, what does getattr(e, 'x', 'default') return when x is undefined everywhere?
The fallback string 'default' — the third argument suppresses the AttributeError.
In C9, after a.kingdom = "Robot", what is Animal.kingdom?
Still "Animalia" — the write created a shadow on the instance, never touching the parent class.
In C10, why does self.count += 1 fail as a counter?
It reads the class value then WRITES onto the instance, giving each object a private count = 1 while the class stays at 0.
In C10, what is BankAccount.count after creating 3 accounts with BankAccount.count += 1?
3 — each constructor bumped the single shared class copy.

Connections

  • OOP Fundamentals
  • Classes and objects
  • The self parameter
  • __init__ constructor
  • Inheritance and MRO
  • Namespaces and scope
  • Mutable vs immutable objects