Exercises — `__init__` constructor — initializing attributes
Before we start, one picture of the whole machine you'll be tested on:

Read it as: calling the class pours arguments in the top; __new__ casts a blank shell; __init__ fills that shell using self; the finished object drops out the bottom. Every exercise below pokes at one part of this pipe.
L1 — Recognition
These check that you can see what __init__ and self are, without running anything complex.
Recall Solution 1.1
Answer: (b).
- (a)
name = nameonly reassigns the local parameter to itself. When__init__returns, that local vanishes — nothing was ever attached to the object. This is Mistake 1 from the parent. - (b)
self.name = namewrites an attribute onto the specific objectselfpoints at. That is exactly what "store on the object" means. ✅ - (c)
Person.name = namesets a class attribute shared by every instance, not this one — a different thing (see Class vs Instance Attributes). Wrong for "on the object."
Recall Solution 1.2
False. __init__ only initializes (fills) an object that ==__new__== already created. Look again at figure s01: the shell is cast before __init__ runs. See __new__ vs __init__.
Recall Solution 1.3
It must return ==None==. Returning anything else raises TypeError. In practice you write no return statement at all — a function with no return returns None automatically.
L2 — Application
Now you run the machinery and predict outputs.
Recall Solution 2.1
__init__ runs with w=5, h=3. It stores self.w = 5, self.h = 3, and computes self.area = 5 * 3 = 15.
Output: 15 5 3.
Note area is computed once at build time (Worked Example 3 in the parent). If w changed later, area would go stale — that's when you'd reach for Properties and computed attributes.
Recall Solution 2.2
a omits balance, so the ==default 100== is used. b passes 500 explicitly, overriding the default.
Output: 100 500.
Recall Solution 2.3
.
Output: 212.0 (float, because / always yields a float in Python 3).
L3 — Analysis
Trace, find the bug, explain why.
Recall Solution 3.1
Output: ['apple'] — not the empty [] you might expect!
Why: the default [] is created once at definition time and lives on the function object. Both c1 and c2 (which used the default) got self.items pointing at that same shared list. c1.add("apple") mutated it, so c2 sees the apple too. This is the Mutable default argument pitfall.
Fix:
def __init__(self, items=None):
self.items = items if items is not None else []Now each object that omits items gets a brand-new list.
Recall Solution 3.2
Line y = y only reassigns the local y to itself — nothing is attached to the object. So p has an x but no y.
print(p.x, p.y) → prints 2, then raises AttributeError: 'Point' object has no attribute 'y'.
Fix: self.y = y. (Parent Mistake 1.)
Recall Solution 3.3
Two things go wrong. First, Dog.__init__("Rex") passes the string "Rex" as self — so inside, self.name = name tries to read a second argument name that was never given → TypeError: __init__() missing 1 required positional argument: 'name'.
Even if it had the right arg count, __init__ returns None, so d would be None — never the object.
Correct way: d = Dog("Rex"). Calling the class runs the full __new__ + __init__ pipeline of figure s01; calling __init__ by hand skips the shell entirely.
L4 — Synthesis
Build new classes that combine the ideas.
Recall Solution 4.1
class Percentage:
def __init__(self, value):
if value < 0 or value > 100:
raise ValueError("must be 0..100")
self.value = value- Why validate in
__init__? It is the single gateway everyPercentagepasses through, so no invalid object can ever exist (parent Worked Example 4). Percentage(150)→ raisesValueError(150 > 100); no object is created.Percentage(40).value→40.
Recall Solution 4.2
class Playlist:
def __init__(self, songs=None):
self.songs = songs if songs is not None else []
self.count = len(self.songs)Playlist(["a","b","c"]).count→3.Playlist().count→0(fresh empty list, length 0). Note we readlen(self.songs)after the safe assignment, so the count always matches the real list.
Recall Solution 4.3
class Wallet:
def __init__(self, owner, start):
self.owner = owner
self.account = Account(start) # a full Cls(args) pipeline runs here- Inside
Wallet.__init__, the lineAccount(start)runs the whole figure-s01 pipeline again (its own__new__+__init__), producing a finishedAccountwhich we attach. Wallet("Asha", 250).account.balance→250.
L5 — Mastery
Everything at once: trace the pipeline, reason about None-return, and combine safe defaults with validation.
Recall Solution 5.1
The parent's formula unrolls to:
o = Point.__new__(Point) # 1. blank shell, no x/y yet
Point.__init__(o, 2, 7) # 2. fills it: self.x=2, self.y=7
p = o # 3. hand back the finished objectAfter this, p.x, p.y → (2, 7). Compare to figure s01: step 1 casts, step 2 fills, step 3 delivers.
Recall Solution 5.2
class Student:
def __init__(self, name, grades=None):
grades = grades if grades is not None else [] # safe default first
for g in grades:
if g < 0 or g > 100:
raise ValueError("grade out of range") # validate before storing
self.name = name
self.grades = grades
self.average = sum(grades) / len(grades) if grades else 0Student("Asha", [80, 90, 100]).average→ .Student("Bo", [50, 150])→150 > 100triggersValueError; no object created.Student("Cy").average→ grades defaults to a fresh[], so theif grades else 0branch gives0. This one problem exercises all four parent lessons:self-storage, safe defaults, validation-as-gateway, and computed attributes.
Recall Solution 5.3
In figure s01, it is ==type.__call__== (triggered by Point()), not __init__, that hands the object back. Python enforces __init__ returning ==None==; returning anything non-None raises TypeError. You never need to return self — the pipeline already returns o for you in its final step. Rule: __init__ fills, the call delivers.
Connections
- 2.1.02 `__init__` constructor — initializing attributes (Hinglish) — the parent topic these exercises drill.
- self and instance attributes — every
self.x = ...above. - __new__ vs __init__ — the two-step pipeline of figure s01.
- Class vs Instance Attributes — why
Person.namediffers fromself.name(Ex 1.1). - Properties and computed attributes — the alternative to computing
area/averagein__init__. - Mutable default argument pitfall — Ex 3.1 in depth.
- Dunder methods —
__init__as one of many. - OOP Fundamentals — the chapter home.