नेस्टेड डेटा — डिक्शनरी की लिस्ट
असली डेटा का आकार: लिस्ट के अंदर डिक्शनरी, डिक्शनरी के अंदर लिस्ट। अंदर तक पहुँचना, ग्रुप बनाना, और copy() यहाँ काफ़ी क्यों नहीं है।
आप कैसे सीखना चाहते हैं?
वह समस्या जिसे हम हल कर रहे हैं
पिछले कुछ अध्यायों में चार कंटेनर सीखे गए — लिस्ट, टपल, डिक्शनरी, सेट। हर उदाहरण में एक ही कंटेनर, एक ही लेवल पर।
असली डेटा ऐसे नहीं आता। ऑर्डर की एक लिस्ट कुछ ऐसी आती है:
customer item quantity price
rafi pen 3 15.0
ahmed bag 1 850.0यहाँ दो लेवल हैं। बाहर एक कलेक्शन — कितने ऑर्डर होंगे पता नहीं, इसलिए लिस्ट। अंदर हर ऑर्डर अपने आप में एक रिकॉर्ड है, जिसके हर हिस्से का एक नाम है — इसलिए डिक्शनरी।
किसी API से जो आता है, CSV फ़ाइल पढ़ने पर जो मिलता है, डेटाबेस जो लौटाता है — लगभग हमेशा यही आकार होता है: डिक्शनरी की एक लिस्ट। आज के अध्याय में कोई नया कंटेनर नहीं है। बस उन्हें एक-दूसरे के अंदर रखकर इस्तेमाल करना है, और उससे होने वाली गलतियों को पहचानना है।
इस अध्याय के अंत तक आप कर पाएंगे
- डिक्शनरी की लिस्ट के अंदर पहुँचना और उस पर लूप चलाना
- गहराई में आए
KeyErrorयाIndexErrorको पढ़कर बताना कि किस लेवल पर गड़बड़ है - डिक्शनरी की लिस्ट से ग्रुप बनाकर हिसाब निकालना
.get()के डिफ़ॉल्ट से अधूरे रिकॉर्ड सँभालना- बताना कि नेस्टेड डेटा में
copy()काफ़ी क्यों नहीं है
पहले से क्या आना चाहिए: सेट — बिना डुप्लिकेट वाला कलेक्शन।
आकार पहचानना
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
print(len(orders))
print(orders[0])
print(orders[0]["customer"])
print(orders[1]["price"])2
{'customer': 'rafi', 'item': 'pen', 'quantity': 3, 'price': 15.0}
rafi
850.0orders[0]["customer"] वाली लाइन ही इस अध्याय का मुख्य हुनर है, और इसे बाएँ से दाएँ, एक-एक कदम पढ़ना होता है:
orders— एक लिस्टorders[0]— उसकी पहली चीज़, जो एक डिक्शनरी हैorders[0]["customer"]— उस डिक्शनरी कीcustomerkey की वैल्यू
शक हो तो रुककर बीच वाला कदम प्रिंट करके देखें। print(orders[0]) लिखकर आँखों से देखना कि वह एक डिक्शनरी है — यह एक आदत नेस्टेड डेटा की आधी समस्याएँ खत्म कर देती है।
लूप से घूमना
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
for order in orders:
total = order["quantity"] * order["price"]
print(f"{order['customer']:<8} {order['item']:<6} {total:>8.2f}")rafi pen 45.00
ahmed bag 850.00for order in orders हर बार एक डिक्शनरी देता है, और उसके अंदर नाम से पहुँचा जा सकता है। इंडेक्स का हिसाब कहीं नहीं है।
f-string के अंदर की कोटेशन। f"{order['customer']}" लाइन में अंदर सिंगल कोटेशन इस्तेमाल हुई है, क्योंकि बाहर की f-string डबल कोटेशन में लिखी है। अंदर-बाहर एक ही तरह की कोटेशन दें तो Python समझ नहीं पाएगा कि स्ट्रिंग कहाँ खत्म हुई। एक तरह की अंदर रखें, दूसरी बाहर।अधूरे रिकॉर्ड
असली डेटा में हर रिकॉर्ड में हर key नहीं होती:
orders = [{"customer": "rafi"}]
print(orders[0]["discount"])KeyError: 'discount'KeyError बता रहा है कि key नहीं है, लेकिन किस रिकॉर्ड में नहीं है यह नहीं बताता। बारह हज़ार ऑर्डर में से किसी एक में ऐसा हो तो ढूँढना मुश्किल है। enumerate से लूप चलाएँ तो हर रिकॉर्ड का नंबर हाथ में रहता है, इसलिए जिसमें key नहीं है उसे सीधे प्रिंट किया जा सकता है:
orders = [
{"customer": "rafi", "discount": 5},
{"customer": "ahmed", "discount": 0},
{"customer": "dia"},
]
for i, order in enumerate(orders):
if "discount" not in order:
print("record", i, "has no discount:", order)record 2 has no discount: {'customer': 'dia'}अब पता चल गया कि समस्या रिकॉर्ड नंबर 2 में है (गिनती शून्य से शुरू होती है), और वह रिकॉर्ड कैसा दिखता है।
अगर key का न होना सामान्य है, तो .get():
orders = [{"customer": "rafi"}]
print(orders[0].get("discount", 0))015 नंबर अध्याय का नियम यहाँ भी लागू है: होना ज़रूरी हो तो स्क्वेयर ब्रैकेट, न होना सामान्य हो तो .get()। नेस्टेड डेटा में लालच ज़्यादा होता है, क्योंकि .get() से हर एरर चुप कराया जा सकता है — लेकिन तब अधूरा डेटा चुपचाप शून्य बनकर हिसाब में घुस जाता है।
दूसरा आकार — डिक्शनरी के अंदर लिस्ट
by_customer = {
"rafi": ["pen", "ink"],
"ahmed": ["bag"],
}
by_customer["rafi"].append("bottle")
by_customer["dia"] = ["eraser"]
print(by_customer)
for customer, items in by_customer.items():
print(f"{customer}: {len(items)} item(s) - {', '.join(items)}"){'rafi': ['pen', 'ink', 'bottle'], 'ahmed': ['bag'], 'dia': ['eraser']}
rafi: 3 item(s) - pen, ink, bottle
ahmed: 1 item(s) - bag
dia: 1 item(s) - eraserby_customer["rafi"].append("bottle") वाली लाइन एक लाइन में दो कदम है: पहले by_customer["rafi"] एक लिस्ट निकालकर लाता है, फिर उस लिस्ट में append होता है।
यह आकार 15 नंबर अध्याय की एक समस्या का जवाब है — वहाँ कहा गया था कि एक key एक ही बार हो सकती है, इसलिए एक व्यक्ति के कई रिकॉर्ड नहीं रखे जा सकते। वैल्यू के रूप में एक लिस्ट रखें तो रखे जा सकते हैं।
ग्रुप बनाना — सबसे काम का पैटर्न
डिक्शनरी की लिस्ट से “किसने क्या-क्या खरीदा” बनाना:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
customer = order["customer"]
if customer not in grouped:
grouped[customer] = []
grouped[customer].append(order["item"])
print(grouped){'rafi': ['pen', 'ink'], 'ahmed': ['bag']}बीच की तीन लाइनें ही पूरी तरकीब हैं: key न हो तो पहले एक खाली लिस्ट रख दें, फिर बेफ़िक्र होकर append करें। ऐसा न करते तो पहले ऑर्डर पर ही KeyError आता।
यही चीज़ एक लाइन में भी लिखी जा सकती है:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
grouped.setdefault(order["customer"], []).append(order["item"])
print(grouped){'rafi': ['pen', 'ink']}setdefault(key, []) कहता है “यह key हो तो उसकी वैल्यू दो, न हो तो यह खाली लिस्ट रखकर फिर वह दो” — और जो लौटता है उसी में append किया जाता है। .get() से फ़र्क अहम है: .get() डिक्शनरी में कुछ रखता नहीं, setdefault रखता है।
तीन लाइन वाला रूप पढ़ने में आसान है, एक लाइन वाला लिखने में तेज़। असली कोड में दोनों मिलेंगे।
और गहराई में
shop = {
"name": "Corner Store",
"staff": [
{"name": "rafi", "shifts": ["mon", "tue"]},
{"name": "dia", "shifts": ["wed"]},
],
}
print(shop["staff"][0]["shifts"][1])
print(len(shop["staff"]))
for person in shop["staff"]:
print(f"{person['name']}: {len(person['shifts'])} shift(s)")tue
2
rafi: 2 shift(s)
dia: 1 shift(s)shop["staff"][0]["shifts"][1] — चार कदम: डिक्शनरी → लिस्ट → डिक्शनरी → लिस्ट। बाएँ से दाएँ पढ़ें तो जटिल नहीं, बस लंबा है।
तीन लेवल से ज़्यादा गहराई हो तो रुककर सोचें। लंबी चेन को तोड़कर बीच के कदम को एक नाम दे दें तो कोड पढ़ा जा सकता है:
first_person = shop["staff"][0]
print(first_person["shifts"][1])नेस्टेड डेटा में copy() काफ़ी नहीं है
12 नंबर अध्याय में देखा था कि b = a दो नामों को एक ही लिस्ट से जोड़ देता है, और copy() उसका हल है। नेस्टेड डेटा में copy() सिर्फ़ एक लेवल बचाता है:
import copy
template = {"name": "", "items": []}
a = template.copy()
b = copy.deepcopy(template)
a["items"].append("pen")
b["items"].append("bag")
print(template)
print(a)
print(b){'name': '', 'items': ['pen']}
{'name': '', 'items': ['pen']}
{'name': '', 'items': ['bag']}a बना था copy() से — एक नई बाहरी डिक्शनरी, लेकिन अंदर की लिस्ट वही लिस्ट है। इसलिए a["items"].append("pen") ने मूल template को भी बदल दिया।
b बना था deepcopy() से, जो अंदर की हर चीज़ की भी कॉपी बनाता है। इसलिए b का बदलाव कहीं नहीं फैला।
copy एक मॉड्यूल है, इसलिए इस्तेमाल से पहले import copy चाहिए — मॉड्यूल के बारे में विस्तार से 22 नंबर अध्याय में।
deepcopyकब चाहिए? जब आप किसी नेस्टेड ढाँचे की कॉपी लेकर उसे बदल रहे हों, और मूल को जैसा का तैसा रखना चाहते हों। ध्यान रखें किdeepcopyधीमा है, और ज़्यादातर समय इसकी ज़रूरत भी नहीं होती — ज़्यादातर कोड डेटा बदलता नहीं, नया डेटा बनाता है।
एक पूरा उदाहरण
orders.py:
# The shape data actually arrives in: a list of records
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
{"customer": "rafi", "item": "ink", "quantity": 2, "price": 120.0},
{"customer": "dia", "item": "pen", "quantity": 5, "price": 15.0},
]
print("Customer Item Qty Total")
grand_total = 0.0
for order in orders:
total = order["quantity"] * order["price"]
grand_total = grand_total + total
print(f"{order['customer']:<9} {order['item']:<6} {order['quantity']:>3} {total:>10.2f}")
print(f"{'':<20}{grand_total:>10.2f}")
print()
# Group the spend by customer: the key may not exist yet, so .get supplies a start
spend = {}
for order in orders:
customer = order["customer"]
spend[customer] = spend.get(customer, 0.0) + order["quantity"] * order["price"]
# Sort by amount by putting the amount first in a tuple
pairs = []
for customer, amount in spend.items():
pairs.append((amount, customer))
pairs.sort(reverse=True)
print("Spend per customer, highest first:")
for amount, customer in pairs:
print(f" {customer:<8} {amount:>8.2f}")
distinct = set()
for order in orders:
distinct.add(order["item"])
print()
print("Distinct items:", sorted(distinct))
print("Best customer :", pairs[0][1])Customer Item Qty Total
rafi pen 3 45.00
ahmed bag 1 850.00
rafi ink 2 240.00
dia pen 5 75.00
1210.00
Spend per customer, highest first:
ahmed 850.00
rafi 285.00
dia 75.00
Distinct items: ['bag', 'ink', 'pen']
Best customer : ahmedइस छोटे प्रोग्राम में पिछले कुछ अध्यायों के चारों कंटेनर काम आए हैं, और हर एक ठीक वहीं जहाँ उसकी जगह है।
लिस्ट — ऑर्डर, क्योंकि कितने होंगे पता नहीं और क्रम है।
डिक्शनरी — हर ऑर्डर, क्योंकि हर हिस्से को एक नाम चाहिए; और spend, क्योंकि वहाँ नाम के हिसाब से जमा हो रहा है।
टपल — pairs की हर जोड़ी। और यहाँ एक तरकीब है: रकम को टपल में पहले रखा गया है, क्योंकि Python टपल को पहली वैल्यू के हिसाब से सॉर्ट करता है। इसलिए सादा .sort(reverse=True) ही रकम के हिसाब से सॉर्ट कर देता है, और key= की ज़रूरत नहीं पड़ती।
सेट — distinct, क्योंकि सवाल ही था “कितनी तरह के”, और pen दो बार आया है।
और spend.get(customer, 0.0) वाली लाइन 15 नंबर अध्याय के गिनती वाले पैटर्न का ही दूसरा रूप है — बस एक-एक बढ़ाने की जगह रकम जुड़ रही है।
जब कुछ टूट जाए
KeyError: 'discount' — लेकिन किस रिकॉर्ड में? for i, order in enumerate(orders): से लूप चलाएँ, तो एरर के समय i पता रहता है — ऊपर वाले उदाहरण की तरह। या लूप के अंदर print(order) रख दें; आखिरी जो प्रिंट हुआ वही दोषी है।
TypeError: string indices must be integers लगभग हमेशा इसका मतलब है कि आपने सोचा हाथ में एक डिक्शनरी है, लेकिन है एक स्ट्रिंग। जैसे अगर orders डिक्शनरी की लिस्ट न होकर नामों की लिस्ट हो, तो order["customer"] यही मैसेज देगा। print(type(order)) से देख लें।
TypeError: list indices must be integers or slices, not str उल्टी गलती — हाथ में लिस्ट है, लेकिन नाम से ढूँढा जा रहा है। शायद एक [0] छूट गया है: orders["customer"] नहीं, orders[0]["customer"]।
AttributeError: 'list' object has no attribute 'get' उसी तरह की गलती, दूसरे रूप में। जिस चीज़ पर आप .get() चला रहे हैं वह डिक्शनरी नहीं, लिस्ट है।
एक कॉपी बदली, मूल भी बदल गया copy() shallow कॉपी बनाता है — अंदर की लिस्ट या डिक्शनरी साझा रहती हैं। import copy करके copy.deepcopy(...) इस्तेमाल करें।
ग्रुप बनाते समय पहली बार में ही KeyError grouped[key].append(...) लिखने से पहले key बनाई ही नहीं गई। if key not in grouped: से खाली लिस्ट रखें, या setdefault इस्तेमाल करें।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
दो लेवल अंदर तक पहुँचा गया है। क्या प्रिंट होगा?
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
]
print(orders[1]["item"])- Apen
- Bbag
- Cahmed
- Dएक `KeyError`
ग्रुप बनाने की कोशिश। पहली बार में ही क्या होगा?
orders = [
{"customer": "rafi", "item": "pen"},
]
grouped = {}
for order in orders:
grouped[order["customer"]].append(order["item"])
print(grouped)- A`AttributeError` — डिक्शनरी में `append` नहीं होता
- B`KeyError: 'rafi'` — key अभी है ही नहीं, इसलिए उसमें `append` नहीं हो सकता
- C`{}` प्रिंट करता है
- Dकाम करता है, `{'rafi': ['pen']}` प्रिंट करता है
copy() लेकर कॉपी में दो बदलाव किए गए हैं। मूल का क्या हाल है?
import copy
template = {"name": "", "items": []}
a = template.copy()
a["name"] = "rafi"
a["items"].append("pen")
print(template)- A{'name': '', 'items': ['pen']}
- B{'name': '', 'items': []}
- C{'name': 'rafi', 'items': ['pen']}
- D{'name': 'rafi', 'items': []}
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
library.py नाम की एक फ़ाइल लिखें, जिसमें कम से कम छह किताबों की एक लिस्ट हो, और हर किताब एक डिक्शनरी हो — शीर्षक, लेखक, साल और कॉपी की संख्या।
फिर:
- सभी किताबें कॉलम में सजाकर प्रिंट करें
- कुल कॉपी और औसत साल निकालें
- लेखक के हिसाब से ग्रुप बनाएँ —
{"author": ["title", "title"]}के आकार में — और हर लेखक की कितनी किताबें हैं प्रिंट करें - कितने अलग-अलग लेखक हैं, सेट से निकालें
- सबसे ज़्यादा कॉपी वाली किताब का शीर्षक प्रिंट करें
फिर असली डेटा की तरह इसे तोड़ें:
- एक किताब से
"year"key पूरी तरह हटा दें, फिर प्रोग्राम चलाएँ। किस लाइन पर रुका, और क्या मैसेज ने बताया कि किस किताब में समस्या है? - अब उसी लाइन पर
.get("year", 0)इस्तेमाल करके फिर चलाएँ। अब औसत साल क्या आया, और क्या वह सच है?
आखिरी सवाल ही असली सीख है। .get() ने एरर रोक दिया, लेकिन एक गायब साल को शून्य मानकर औसत निकालना एक गलत जवाब है — चुपचाप। एरर हटाना और समस्या हल करना एक ही बात नहीं है।
चरण 6 / 6
चुनौती — अध्याय की क्विज़
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
क्विज़ देने के लिए साइन इन करें