2.1.4 · D5OOP Fundamentals
Question bank — Instance methods, class methods (`@classmethod`), static methods (`@staticmethod`)
This bank builds on the parent topic and touches Instance vs Class attributes, Inheritance and method resolution order (MRO), Descriptors and the __get__ protocol, Decorators, and the Factory design pattern.
Before we start, three plain-word anchors so no term below is unearned:
- Instance = one specific object made from the class (one cookie from the cutter).
- Class = the blueprint/cutter itself, holding shared data.
- Bound method = the little package Python hands you when you write
obj.method— it has quietly remembered which object (or class) to slip in as the first argument.
True or false — justify
TF · An instance method can read and modify class attributes.
True. Through
self.PI (read) or Circle.PI = ... / type(self).PI = ... (write), an instance method reaches class state; it simply also has access to per-object state.TF · A @classmethod cannot access any per-object (instance) data.
True. Its first argument is
cls (the class), so there is no self in scope — it never sees an individual object's attributes.TF · A @staticmethod is just a normal function that happens to live inside a class.
True. It receives no automatic first argument; the only thing the class gives it is a namespace (
Circle.is_valid).TF · self is a Python keyword that the language reserves.
False.
self is only a naming convention; it is the first positional parameter. You could name it this, but everyone would hate you.TF · Calling c.area() and calling Circle.area(c) do exactly the same thing.
True.
c.area produces a bound method that inserts c as the first argument, so both routes run area with self = c.TF · Decorating a method with @classmethod changes how attribute access (__get__) behaves for that name.
True.
@classmethod swaps the descriptor so its __get__ injects type(obj) instead of obj — same mechanism, different "what do I inject" rule.TF · You can call a @classmethod on an instance, not just on the class.
True.
c.unit() works; Python still passes the class as cls, deriving it from type(c), not the object itself.TF · A @staticmethod respects inheritance the same way a @classmethod does.
False. A static method has no
cls, so it can't know which subclass called it — it can only hard-code a class name, which is exactly why factories should be classmethods.TF · Writing self.PI = 3.14 inside a method changes the class attribute Circle.PI for all objects.
False. Assignment via
self always creates a new instance attribute that shadows the class one; other objects still see the original Circle.PI.Spot the error
Error · def area(): return self.r inside a class.
The
self parameter is missing. Python passes the object as the first positional argument, so the slot must exist; otherwise you get "takes 0 positional arguments but 1 was given".Error · A factory written as @staticmethod def unit(): return Circle(1).
It hard-codes
Circle, so a subclass Ball.unit() still returns a Circle. Use @classmethod def unit(cls): return cls(1) so the factory follows inheritance.Error · @classmethod def describe(cls): return f"I am a {Animal.species}".
It reads
Animal.species instead of cls.species, so every subclass reports the parent's value. Use cls.species to honour the actual calling class.Error · Putting @staticmethod on a method whose body uses self.r.
A static method receives no first argument, so
self is undefined — this raises NameError. If it needs object data, it must be a plain instance method.Error · class A: @classmethod def f(self): ... — using self as the classmethod's parameter name.
It's not a syntax error, but it's misleading: the argument is the class, not an instance. Name it
cls so readers know what they're actually getting.Error · Expecting @staticmethod def helper(): ... to modify a class attribute via helper alone.
The static method has no
cls and no self, so it has no reference to the class; it would have to name the class explicitly, defeating the point of keeping it decoupled.Error · Defining two __init__ methods to allow "two constructors".
The second definition simply overwrites the first — a class has exactly one
__init__. Extra constructors should be @classmethod alternate constructors like from_string.Why questions
Why · Why does self "appear automatically" even though you never pass it at the call site?
Because
obj.method triggers the function's __get__, returning a bound method that stores obj; calling it slips obj in as the first positional argument.Why · Why prefer @classmethod over hard-coding the class name for alternate constructors?
cls is whatever class the call came through, so subclasses build subclass instances automatically; a hard-coded name would ignore inheritance.Why · Why is @staticmethod ever useful if it gets neither self nor cls?
It groups logically related helper logic inside the class's namespace (e.g. validation), improving discoverability and organisation without needing any object or class context.
Why · Why can reading self.PI succeed while there is no per-object PI, yet writing self.PI doesn't touch the class?
Attribute lookup falls back from instance to class, so reads find
Circle.PI; assignment never falls back — it always creates the name on the instance.Why · Why are all three method kinds described as "the same mechanism"?
All are stored as descriptors implementing
__get__; the only difference is what __get__ injects — the instance, the class, or nothing.Why · Why does calling a classmethod through an instance still give cls and not self?
The classmethod descriptor is written to inject
type(obj), so it deliberately discards the object and hands over the class regardless of how it was reached.Edge cases
Edge · What happens if a class attribute and an instance attribute share a name (e.g. both PI)?
The instance attribute wins on lookup for that object (it shadows the class one); other objects without the instance attribute still see the class value.
Edge · What does type(a).__dict__['f'].__get__(a, type(a)) return for a plain method?
A bound method object that remembers
a; calling it runs f(a), which is precisely where self comes from.Edge · Can a @classmethod create instances of a subclass without knowing that subclass exists?
Yes. Because it calls
cls(...) and cls is filled in at call time, Sub.make() produces a Sub even though the classmethod was defined on the parent.Edge · What error appears when you call c.area() on a method defined without self?
TypeError: area() takes 0 positional arguments but 1 was given — Python still tried to pass c into the missing slot.Edge · If a subclass overrides a class attribute, which value does a parent-defined classmethod read when called via the subclass?
The subclass's value, because
cls.attr resolves through cls (the subclass) and its MRO, not through the parent that defined the method.