2.1.2 · D4OOP Fundamentals

Exercises — `__init__` constructor — initializing attributes

2,384 words11 min readBack to topic

Before we start, one picture of the whole machine you'll be tested on:

Figure — `__init__` constructor — initializing attributes

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 = name only 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 = name writes an attribute onto the specific object self points at. That is exactly what "store on the object" means. ✅
  • (c) Person.name = name sets 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 every Percentage passes through, so no invalid object can ever exist (parent Worked Example 4).
  • Percentage(150) → raises ValueError (150 > 100); no object is created.
  • Percentage(40).value40.
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"]).count3.
  • Playlist().count0 (fresh empty list, length 0). Note we read len(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 line Account(start) runs the whole figure-s01 pipeline again (its own __new__ + __init__), producing a finished Account which we attach.
  • Wallet("Asha", 250).account.balance250.

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 object

After 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 0
  • Student("Asha", [80, 90, 100]).average.
  • Student("Bo", [50, 150])150 > 100 triggers ValueError; no object created.
  • Student("Cy").average → grades defaults to a fresh [], so the if grades else 0 branch gives 0. 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.name differs from self.name (Ex 1.1).
  • Properties and computed attributes — the alternative to computing area/average in __init__.
  • Mutable default argument pitfall — Ex 3.1 in depth.
  • Dunder methods__init__ as one of many.
  • OOP Fundamentals — the chapter home.