1.2.29 · D3 · Coding › Introduction to Programming (Python) › [[1.2.29 - args and - kwargs — flexible argument passing| - args and - kwargs — flexible argument passing]]
Intuition Yeh page kya hai
Parent note ne tumhe sikhaya kya karta hai *args aur **kwargs. Yeh page ek stress test hai: hum har woh situation list karte hain jo yeh operators tumhare saamne rakh sakte hain — empty inputs, mixed inputs, collisions, dono sides pe unpacking, woh exam trick jo sabko pakad leti hai — aur phir ek-ek example solve karte hain har cell ke liye. Agar tum neeche ke sab das outputs predict kar sako, toh tum yeh topic master kar chuke ho.
Do words ki ek choti reminder jo hum baar baar use karenge:
positional argument — ek value jo position (order) se pass hoti hai, jaise 2 in add(2, 3).
keyword argument — ek value jo naam se pass hoti hai, jaise x=9 in f(x=9).
Har baar jab tum ek flexible function ko call karte ho, usse ek grid pe ek point ki tarah socho. Ek axis hai kitne positional values aate hain; doosra axis hai kitne keyword values aate hain; aur kuch special "edge" columns hain weird cases ke liye. Neeche woh grid ek checklist mein flatten hai — har row ek alag cheez hai jo galat ja sakti hai ya tumhe surprise kar sakti hai.
Cell
Scenario class
Covered by
A
*args catches bahut saare extra positionals
Ex 1
B
*args catches zero (empty tuple ()) — degenerate
Ex 2
C
**kwargs catches bahut saare keywords
Ex 3
D
Named param + *args + **kwargs ek saath (the split)
Ex 4
E
Call-site unpacking : ek list ko * se spread karo, dict ko ** se
Ex 5
F
Collision : same key do baar diya gaya → TypeError
Ex 6
G
Keyword-only parameters (woh akela * separator)
Ex 7
H
Dict-merge with {**a, **b} — baad wali key jeet ti hai (override)
Ex 8
I
Real-world word problem: ek shopping-cart total
Ex 9
J
Exam twist: call site pe * bhool jaana (single-arg trap)
Ex 10
Har numeric ya structural answer neeche verify block mein machine-checked hai.
Upar figure dekho: incoming values left se right flow karti hain. Named slots pehle fill hote hain (black), phir baaki bache positionals *args tuple mein pour hote hain (red), aur koi bhi stray name=value pairs **kwargs dict mein chale jaate hain. Har example ke liye yeh picture dimaag mein rakho.
Worked example Ex 1 — kisi bhi count ke numbers ka sum
def total ( * args):
return sum (args)
print (total( 2 , 3 , 4 , 5 ))
Forecast: total "officially" kitne arguments accept karta hai, aur args ka shape kya hai?
def total(*args) function ko koi fixed positional slots nahi deta. Yeh step kyun? * kehta hai "koi named slots nahi hain fill karne ke liye; sab kuch ek tuple mein sweep karo."
Chaar values 2, 3, 4, 5 aur kahin nahi ja sakti, toh Python unhe pack karta hai: args = (2, 3, 4, 5). Yeh step kyun? Leftover positionals hamesha ek tuple ban jaati hain — yeh * ka "collect" role hai.
sum(args) tuple ke elements add karta hai: 2+3+4+5 = 14. Yeh step kyun? sum kisi bhi iterable pe chalta hai, aur ek tuple iterable hai.
Verify: 2+3+4+5 = 14. ✅ Answer: 14.
Worked example Ex 2 — empty-tuple edge case
def total ( * args):
return sum (args)
print (total())
Forecast: Error? None? 0? Kuch aur?
Hum total() ko koi argument ke bina call karte hain. Yeh step kyun? Yeh degenerate input hai — matrix ki boundary — aur beginners crash expect karte hain.
Python phir bhi args banata hai, lekin ab yeh empty tuple () hai. Yeh step kyun? *args kabhi "too few" pe error nahi karta; catch-all simply kuch nahi pakadta.
sum(()) definition se 0 hai (koi numbers nahi hain ka sum). Yeh step kyun? sum 0 se shuru hota hai aur kuch nahi jodta, toh start value return karta hai.
Verify: sum(()) == 0. ✅ Answer: 0. (Yahaan if not args: guard ki zaroorat bhi nahi — lekin tum add karte agar divide karna hota, jaise parent ke mean example mein.)
Worked example Ex 3 — labelled data collect karna
def profile ( ** kwargs):
return list (kwargs.items())
print (profile( name = "Ravi" , age = 20 , city = "Pune" ))
Forecast: kwargs kis type ka hai, aur .items() kya deta hai?
def profile(**kwargs) koi named parameters declare nahi karta, sirf double-star catch-all. Yeh step kyun? Har incoming key=value ka koi matching slot nahi hai, toh sab kwargs mein chale jaate hain.
Python dict banata hai kwargs = {'name':'Ravi', 'age':20, 'city':'Pune'}. Yeh step kyun? Yeh "keywords collect karo → dict" role hai — names ke liye *args ka mirror.
.items() (key, value) pairs yield karta hai; list(...) mein wrap karne se woh ek list of tuples ban jaate hain. Yeh step kyun? Hume ek concrete, printable, order-preserving result chahiye (dicts Python 3.7+ mein insertion order maintain karte hain).
Verify: list == [('name','Ravi'), ('age',20), ('city','Pune')]. ✅
Worked example Ex 4 — ek named, baaki split
def f (a, * args, ** kwargs):
return a, args, kwargs
print (f( 1 , 2 , 3 , x = 9 , y = 10 ))
Forecast: Teeno buckets draw karo. 1, 2, 3, x=9, y=10 mein se har value kahaan jaati hai?
a ek normal named positional hai; yeh pehli positional value pakadta hai → a = 1. Yeh step kyun? Named slots catch-alls se pehle fill hote hain (figure dekho: black slots pehle fill hote hain).
2 aur 3 leftover positionals hain → args = (2, 3). Yeh step kyun? Jab a satisfy ho jaata hai, remaining positionals tuple mein sweep ho jaate hain.
x=9 aur y=10 keywords hain jinka koi named slot nahi hai → kwargs = {'x':9, 'y':10}. Yeh step kyun? Named keywords bina matching parameter ke double-star dict mein chale jaate hain.
Verify: result == (1, (2, 3), {'x':9, 'y':10}). ✅
Worked example Ex 5 — ek list aur ek dict ko call
mein spread karna
def volume (length, width, height):
return length * width * height
dims_list = [ 2 , 3 , 4 ]
dims_dict = { 'length' : 2 , 'width' : 3 , 'height' : 4 }
print (volume( * dims_list)) # list spread karo
print (volume( ** dims_dict)) # dict spread karo
Forecast: Dono calls SAME 3-slot function ko target karti hain. Kya dono same number dete hain?
volume(*dims_list) [2,3,4] ko teen alag positionals 2, 3, 4 mein turn karta hai. Yeh step kyun? Ek call pe, * def role ka opposite karta hai: yeh spread karta hai, values ko position se slots se match karta hai.
Toh slots fill hote hain length=2, width=3, height=4 → 2*3*4 = 24. Yeh step kyun? List mein position order parameter order se match karni chahiye — yeh positional spread ka risk hai.
volume(**dims_dict) naam se spread karta hai: har dict key ek parameter name ke barabar honi chahiye. Yeh step kyun? ** keyword se match karta hai, toh dict mein order matter nahi karta — safer lekin stricter.
Verify: dono 24 ke barabar hain. ✅
Worked example Ex 6 — same argument do baar diya gaya
def greet (name):
return f "hi { name } "
args = ( "Ravi" ,)
kwargs = { "name" : "Sara" }
greet( * args, ** kwargs) # kya hoga?
Forecast: "hi Ravi"? "hi Sara"? Ya error?
*args ("Ravi",) spread karta hai → positional name="Ravi". Yeh step kyun? Ek positional single slot name fill karta hai.
**kwargs phir name="Sara" bhi set karne ki koshish karta hai — lekin name already filled hai. Yeh step kyun? Ek parameter do values receive nahi kar sakta; call pe "winner" choose karne ka koi rule nahi hai.
Python raise karta hai TypeError: greet() got multiple values for argument 'name'. Yeh step kyun? Yeh genuine ambiguity error hai — Ex 8 ke dict-merge case se alag, jahan baad wali simply override karti hai.
Verify: hum collision confirm karte hain yeh check karke ki ek positional aur ek same-named keyword conflict karte hain. ✅ (Answer: TypeError , koi valid return value nahi.)
Worked example Ex 7 — woh akela
* jo names force karta hai
def connect (host, * , port, timeout = 30 ):
return host, port, timeout
print (connect( "db" , port = 5432 ))
# connect("db", 5432) # TypeError hoga
Forecast: Bare * ke baad, kya port positionally pass ho sakta hai?
Bina naam ke akela * matlab hai "yahaan ke baad aur positionals allowed nahi hain. " Yeh step kyun? Yeh ek divider hai: iske daayein sab kuch keyword-only hai.
Toh host positional "db" leta hai, lekin port aur timeout zaroor named hone chahiye. connect("db", port=5432) timeout ka default 30 use karta hai. Yeh step kyun? Keyword-only params calls ko self-documenting banate hain aur accidental positional mistakes rokते hain.
Result: ("db", 5432, 30). connect("db", 5432) try karna fail hoga kyunki 5432 ke liye koi positional slot nahi hai. Yeh step kyun? Yahi ordering ka "keyword-only" cell hai positional → *args → keyword-only → **kwargs.
Verify: result == ("db", 5432, 30). ✅
{**a, **b} se override
defaults = { 'color' : 'red' , 'size' : 'M' , 'qty' : 1 }
user = { 'size' : 'L' , 'qty' : 3 }
final = { ** defaults, ** user}
print (final)
Forecast: size aur qty ki kaun si value survive karti hai?
{**defaults, **user} pehle defaults ko ek naye dict mein pour karta hai. Yeh step kyun? Unpacking ka order left-to-right hai, toh defaults base layer bichhaata hai.
Phir user upar se pour hota hai; jahaan keys collide karti hain, baad wali value overwrite karti hai. Yeh step kyun? Ex 6 (ek call) se alag, ek dict literal mein ek clear rule hai — last assignment wins, koi error nahi.
color 'red' rehta hai (sirf defaults mein hai); size → 'L', qty → 3 (user override karta hai). Yeh step kyun? Yeh standard "config defaults + user overrides" pattern hai.
Verify: final == {'color':'red', 'size':'L', 'qty':3}. ✅
Worked example Ex 9 — ek flexible shopping-cart total
Ek shop kisi bhi number of item prices bill karta hai, phir optionally ek percentage discount aur ek flat shipping fee naam se pass hoti hai.
def checkout ( * prices, discount = 0 , shipping = 0 ):
subtotal = sum (prices)
after_discount = subtotal * ( 1 - discount / 100 )
return round (after_discount + shipping, 2 )
print (checkout( 100 , 250 , 50 , discount = 10 , shipping = 40 ))
Forecast: Compute karne se pehle total apne dimaag mein estimate karo.
*prices teen item prices pakadta hai → prices = (100, 250, 50), subtotal = 400. Yeh step kyun? Items ki count har customer ke liye alag hoti hai, toh catch-all exactly sahi hai.
discount aur shipping keyword-only-ish extras hain defaults ke saath. Yahaan discount=10 matlab 10% cut: 400 * (1 - 10/100) = 400 * 0.9 = 360. Yeh step kyun? Percentages *args ke saath cleanly combine hoti hain kyunki woh named hain, positional nahi.
Flat shipping=40 add karo: 360 + 40 = 400.00. Yeh step kyun? Shipping discount ke baad add hoti hai — business logic ka order matter karta hai.
Verify: round(400*0.9 + 40, 2) == 400.0. ✅ Units: currency dollars, do decimals round(..., 2) se.
Worked example Ex 10 — call site pe
* bhool jaana
def total ( * args):
return len (args)
nums = [ 10 , 20 , 30 ]
print (total(nums)) # trap
print (total( * nums)) # fixed
Forecast: Har len(args) kya print karta hai — 3 aur 3? 1 aur 3?
total(nums) list khud ko ek single positional value ke roop mein pass karta hai. Yeh step kyun? Bina * ke, list ek object hai; args = ([10,20,30],) — ek 1-element tuple jiska sirf ek element woh list hai.
Toh len(args) == 1. Yeh step kyun? Humne arguments ke tuple ko measure kiya, aur exactly ek argument hai.
total(*nums) list spread karta hai: args = (10, 20, 30), toh len(args) == 3. Yeh step kyun? Call site pe star hi woh cheez hai jo "ek list" ko "teen values" mein convert karta hai — yahi bug zyaadatar exams test karte hain.
Verify: pehle 1 print hota hai, doosre se 3. ✅
Recall Poore matrix ke liye ek-line self-test
Sab das answers predict karo, phir check karo: 14, 0, pairs ki list, (1,(2,3),{...}), 24 do baar, TypeError, ("db",5432,30), merged dict, 400.0, aur (1, 3).
A: 14 ::: 2+3+4+5 ka sum
B: total() ::: 0 — args empty tuple hai
F: collision ::: TypeError — name ko do values mili
J: total(nums) vs total(*nums) ::: 1 phir 3
Functions and Parameters — woh fixed-slot baseline jise har example bend karta hai.
Default Arguments — discount=0, shipping=0, timeout=30 upar power karta hai.
Tuples — jo *args banata hai (Ex 1, 2, 4, 10).
Dictionaries — jo **kwargs banata hai aur jo {**a,**b} merge karta hai (Ex 3, 8).
Decorators — forwarding pattern Ex 4 aur 5 ko generalise karta hai.
Unpacking Assignment — wahi star idea, = ke left side pe.