1.2.28 · D4 · HinglishIntroduction to Programming (Python)

ExercisesDefault parameters, keyword arguments

2,518 words11 min read↑ Read in English

1.2.28 · D4 · Coding › Introduction to Programming (Python) › Default parameters, keyword arguments

Shuru karne se pehle, do words ka ek-line reminder jo hum constantly use karte hain:


Level 1 — Recognition

Yahan tumhe sirf spot karna hai ki kuch legal hai ya nahi aur output padhna hai. Abhi design nahi.

Har definition line ke liye batao ki Python use accept karta hai ya nahi. Agar nahi, toh broken rule ka naam batao.

def a(x, y=1): ...        # (i)
def b(x=1, y): ...        # (ii)
def c(x, y=1, z=2): ...   # (iii)
def d(x=1, y=2): ...      # (iv)
Recall Solution L1.1

Rule 1 kehta hai: non-default parameters pehle aane chahiye, default waale baad mein (defaults daayein drift karte hain).

  • (i) ✅ legal — x (koi default nahi) y=1 (default) se pehle hai.
  • (ii)SyntaxErrorx=1 (default) y (koi default nahi) se pehle baitha hai. Order ulta hai.
  • (iii) ✅ legal — ek non-default, phir do defaults, sahi order mein.
  • (iv) ✅ legal — saare parameters ke defaults hain, isliye "non-default after default" ki koi problem nahi.

L1.2 — Output padho

def greet(name, greeting="Hello"):
    return greeting + ", " + name
 
print(greet("Ravi"))
print(greet("Ravi", "Namaste"))
print(greet("Ravi", greeting="Hi"))

Teen printed lines likho.

Recall Solution L1.2
  • greet("Ravi")greeting omit hua hai, isliye uska default "Hello" use hoga → Hello, Ravi.
  • greet("Ravi", "Namaste") → doosri value greeting ko position se fill karti hai → Namaste, Ravi.
  • greet("Ravi", greeting="Hi") → keyword seedha slot ko naam deta hai → Hi, Ravi.

Level 2 — Application

Ab tum rules ko apply karoge calls banane ya fix karne ke liye.

L2.1 — Beech wala skip karo

def power(base, exp=2, mod=None):
    r = base ** exp
    return r if mod is None else r % mod

Ek single call likho jo 5 ** 2 compute kare aur phir mod 7 le, bina number 2 kahin likhe. Numeric result do.

Recall Solution L2.1

Tum chahte ho ki exp apna default 2 rakhe, lekin mod set karna hai. Positionally tumhe power(5, 2, 7) likhna padta — jisme 2 type karna padta. Keyword tumhe exp ko jump over karne deta hai:

power(5, mod=7)

Computation: exp 2 rehta hai, toh r = 5 ** 2 = 25; phir 25 % 7 = 4. Result = 4. Isliye hi keyword arguments exist karte hain: ek baad wale knob tak pahuncho aur pehle waale untouched chhod do.

L2.2 — Broken call fix karo

Yeh error raise karta hai:

def box(width, height, x=0, y=0): ...
box(x=1, 5)

Error explain karo aur call ko rewrite karo taaki width=5 aur x=1 set ho.

Recall Solution L2.2

Error: SyntaxError: positional argument follows keyword argument. Jaise hi tum x=1 (keyword) likhte ho, uske baad ka bare 5 ka koi jaana-pehchana slot nahi — Rule 2 kehta hai saare positional arguments kisi bhi keyword argument se pehle aane chahiye. Fix: pehle positionals rakho.

box(5, x=1)      # width=5 (positional), x=1 (keyword)

Yahan width ko 5 milta hai, height/y rakhte hain... ruko — height ka koi default nahi, isliye yeh phir bhi missing-argument error se fail hoga. Sahi, poori call:

box(5, 3, x=1)   # width=5, height=3, x=1, y default 0 rakhta hai

L2.3 — Multiple-values error

Exact error predict karo:

def f(name, x=0): ...
f("Asha", name="Asha")
Recall Solution L2.3

Positional "Asha" pehle se name fill kar chuka hai. Phir name="Asha" usi slot ko dobara fill karne ki koshish karta hai. Result: TypeError: f() got multiple values for argument 'name'. Values equal hona tumhe nahi bachata — Python slot do baar assign hone ki complaint karta hai, values ke disagree karne ki nahi.


Level 3 — Analysis

Ab tum hidden behaviour trace karoge aur explain karoge ki kyun hota hai.

L3.1 — Mutable-default trap

def add(item, bag=[]):
    bag.append(item)
    return bag
 
print(add(1))
print(add(2))
print(add(3))

Teeno lines predict karo aur mechanism explain karo.

Recall Solution L3.1

Output:

[1]
[1, 2]
[1, 2, 3]

Mechanism: default value [] ek baar create hota hai, jab def line run hoti hai — har call par nahi. Isliye jo bhi call bag omit karti hai woh same list object reuse karti hai, aur .append isse badhata rehta hai. Picture yeh hai: ek shared bucket, har call ke liye naya bucket nahi. Figure dekho.

Figure — Default parameters, keyword arguments

L3.2 — Fix karo aur re-predict karo

None sentinel use karke add rewrite karo, phir same teen calls ke liye predict karo.

Recall Solution L3.2
def add(item, bag=None):
    if bag is None:
        bag = []          # IS call par fresh list
    bag.append(item)
    return bag

Ab add(1)[1], add(2)[2], add(3)[3]. Jo call bag omit karti hai woh body ke andar apni nayi list banati hai, isliye kuch shared nahi hai. Figure mein dekho ki buckets kaise separate hote hain:

Figure — Default parameters, keyword arguments

L3.3 — is None kyun, == [] kyun nahi?

L3.2 mein humne if bag is None test kiya. if bag == [] kyun nahi?

Recall Solution L3.3

is None poochta hai: "kya caller chup raha?" — kuch pass nahi hua? == [] poochta hai: "jo bhi pass hua hai kya woh empty hai?" — yeh bilkul alag sawal hai. Agar koi caller apni khud ki empty list legitimately pass kare jo woh fill karwana chahte hain, toh == [] galti se ise throw away kar dega aur ek nayi substitute kar dega. is None caller ki list ka respect karta hai. Isliye: is None absence ko empty-but-real value se alag karta hai.


Level 4 — Synthesis

Ab tum functions design karoge jo ek saath kai constraints satisfy kare.

L4.1 — Logger design karo

log(msg, level="INFO", timestamp=None) likho jo "[INFO] hello" jaisi string return kare. Agar timestamp diya gaya ho (ek string), toh ise prepend karo: "2024 [INFO] hello". Ensure karo ki common case log("hello") chhota rahe.

Recall Solution L4.1
def log(msg, level="INFO", timestamp=None):
    head = f"[{level}] {msg}"
    return head if timestamp is None else f"{timestamp} {head}"
  • log("hello")"[INFO] hello" (dono defaults use hue — chhota 80% case).
  • log("bad", level="ERROR")"[ERROR] bad" (keyword yahan kuch skip nahi karta lekin knob ko clearly naam deta hai).
  • log("hi", timestamp="2024")"2024 [INFO] hi" (keyword use karke level ko jump over karo — same trick jaise L2.1). Note karo ki timestamp=None safe sentinel default hai; level="INFO" safe immutable string default hai.

L4.2 — Trap ke bina design karo

record(name, entries=None) likho jo name ko ek list mein append kare aur return kare, taaki baar baar calls state share na karein. Phir dikhaao ki record("a") aur record("b") independent lists dete hain.

Recall Solution L4.2
def record(name, entries=None):
    entries = [] if entries is None else entries
    entries.append(name)
    return entries
  • record("a")["a"]
  • record("b")["b"] (independent — koi shared bucket nahi)
  • record("c", ["x"])["x", "c"] (caller ki apni list respected aur extend ki gayi). Yeh DRY, safe pattern hai: guard ki ek line, phir normal logic. Yeh DRY principle - Don't Repeat Yourself ko respect karta hai fresh-list logic ko exactly ek jagah rakh ke.

Level 5 — Mastery

Full-integration problems. Ordering rules, keywords, sentinels, aur scope reasoning sab combine karo.

L5.1 — Sab kuch trace karo

def build(a, b=10, c=None):
    if c is None:
        c = []
    c.append(a + b)
    return c
 
x = build(1)
y = build(2, 20)
z = build(3, c=x)
print(x)
print(y)
print(z)

x, y, z predict karo, aur explain karo ki z aisa kyun dikhta hai.

Recall Solution L5.1

Step by step:

  • build(1): b=10 (default), c omit hua → nayi list [], append 1+10=11x = [11].
  • build(2, 20): b=20 positionally, c omit hua → nayi list, append 2+20=22y = [22].
  • build(3, c=x): b=10 (default), c same object x hai. 3+10=13 x mein append karo → list ban jaati hai [11, 13]. Toh z hi x hai. Output:
[11, 13]      # x, last call se mutate hua
[22]          # y, independent
[11, 13]      # z same object hai jaise x

Key insight: koi default galat use nahi hua (humne None sentinel correctly use kiya). Yahan sharing deliberate hai — caller ne purposely x pass kiya, isliye z is x. Yeh Mutable vs immutable objects se connect hota hai: ek mutable object pass karna matlab callee ise change kar sakta hai.

L5.2 — Ek specification repair karo

Ek teammate chahta hai: render(items, sep=", ", prefix=[]) jo items ko join kare unhe prefix mein store karne ke baad. Yeh "kabhi kabhi purane items yaad rakhta hai". Bug diagnose karo, phir rewrite karo taaki kabhi leak na ho, lekin phir bhi koi caller apni prefix list supply kar sake.

Recall Solution L5.2

Diagnosis: prefix=[] ek mutable default hai jo def time par ek baar create hota hai. Agar render prefix.extend(items) kare aur koi caller prefix omit kare, toh aisi har call usi shared list ko mutate karti hai — yahi "purane items yaad rakhna" symptom hai. Rewrite:

def render(items, sep=", ", prefix=None):
    prefix = [] if prefix is None else prefix
    out = list(prefix)          # copy karo taaki caller ki list kabhi mutate na ho
    out.extend(items)
    return sep.join(out)
  • render(["a", "b"])"a, b" (fresh, koi memory nahi).
  • render(["c"], sep=" | ")"c" (single item, keyword sep use hua).
  • render(["z"], prefix=["p"])"p, z" (caller ka prefix respected, aur copy hua taaki unki list untouched rahe). list(prefix) use karna extra-safe touch hai: jab bhi real list pass ho, hum caller ko surprise nahi karte unka object mutate karke.

L5.3 — Design choice explain karo

Python defaults ko ek baar (definition par) kyun evaluate karta hai instead of har call par? Trade-off ek line mein do.

Recall Solution L5.3

Ek baar evaluate karna sasta aur predictable hai: default expression ek baar run hota hai, isliye heavy_computation() jaisa default har call par re-run nahi hota. Trade-off hai mutable-default trap — ek shared object calls ke beech survive karta hai. Python ne speed/predictability choose ki aur tumse expect karta hai ki mutable case ke liye None sentinel use karo. Toh: ek evaluation performance kharidta hai; tum None defaults ki discipline se pay karte ho.



Connections