2025, Oct 07 03:33

csv.DictReader से गेम स्कोर को मज़बूत Python डिक्शनरी में बदलें

गेम स्कोर वाले CSV को csv.DictReader से भरोसेमंद Python डिक्शनरी में बदलना सीखें। KeyError और अनपैकिंग त्रुटि से बचाव, लचीली संरचना व उपयोगी कोड उदाहरण।

गेम स्कोर वाले CSV को साफ-सुथरी Python डिक्शनरी में बदलना सुनने में आसान लगता है, लेकिन KeyError जैसी अप्रत्याशित दिक्कतें और अनपैकिंग से जुड़ी समस्याएँ जल्दी सामने आ जाती हैं। असली उलझन अक्सर इस गलतफहमी से होती है कि csv.DictReader कैसे काम करता है और Python में मान्य नेस्टेड स्ट्रक्चर कैसा दिखता है। चलिए आम गिरहों को समझते हैं और ऐसे भरोसेमंद पैटर्न तक पहुँचते हैं जो बदलते नामों और किसी भी संख्या वाले गेम कॉलम को संभाल सकें।

सेटअप और अड़चनें

मान लें कि आपके पास एक हेडर पंक्ति और बदलती संख्या वाले गेम कॉलम वाला CSV है:

name,Game_1, Game_2, Game_3
James,3,7,4
Charles,2,3,8
Bob,6,2,4

आप इसे csv.DictReader से पढ़ते हैं और खिलाड़ी के नाम को कुंजी बनाकर डिक्शनरी बनाना चाहते हैं। पहली कोशिश कुछ ऐसी दिख सकती है, लेकिन यह गलत है:

import csv
# परिणाम जुटाने के लिए
scorebook = {}
with open("scores.txt") as fh:
    dict_reader = csv.DictReader(fh)
    # गलत: DictReader के साथ पंक्ति को पूर्णांक इंडेक्स से एक्सेस करना
    for rec in dict_reader:
        scorebook[rec[0]] = rec[1]  # KeyError: 0 / KeyError: 1

या रीडर से सीधे आइटम अनपैक करने की कोशिश:

import csv
with open("scores.txt") as fh:
    dict_reader = csv.DictReader(fh)
    for left, right in dict_reader:  # ValueError: अनपैक करने के लिए बहुत अधिक मान (अपेक्षित 2)
        print(left, right)

असल में हो क्या रहा है

csv.DictReader हर पंक्ति को list नहीं, बल्कि dict के रूप में देता है। यही कुंजी है। पंक्ति के मान कॉलम नाम से एक्सेस होते हैं, न कि संख्यात्मक इंडेक्स से। इसलिए rec['name'] मान्य है, जबकि rec[0] मान्य नहीं है और KeyError: 0 फेंकता है क्योंकि 0 उस पंक्ति की dict में कुंजी ही नहीं है।

चाहे गए परिणाम के ढाँचे में भी structural समस्या है। यह अभिव्यक्ति

'James': {'Game_1': '3'}, {'Game_2': '7'}, {'Game_3': '4'}

एक ही dict मान के रूप में मान्य नहीं है। Python में आप इनमें से एक रख सकते हैं: dict की list, dict का tuple, या एक ही dict जो गेम नामों को स्कोर से मैप करे। यानी मान्य रूप हैं:

'James': [ {'Game_1': '3'}, {'Game_2': '7'}, {'Game_3': '4'} ]
'James': ( {'Game_1': '3'}, {'Game_2': '7'}, {'Game_3': '4'} )
'James': {'Game_1': '3', 'Game_2': '7', 'Game_3': '4'}

समाधान: DictReader को आपके लिए काम करने दें

क्योंकि हर पंक्ति पहले से ही dict है, आपको खिलाड़ी निकालने के लिए हेडर नाम का इस्तेमाल करना चाहिए, और फिर या तो प्रति खिलाड़ी गेम्स की एक dict बनानी चाहिए, या छोटे-छोटे dict की list/tuple। यहाँ प्रति खिलाड़ी एक डिक्शनरी वाला तरीका है, जो आमतौर पर सबसे व्यावहारिक रहता है:

import csv
scorebook = {}
with open("scores.txt") as fh:
    dict_reader = csv.DictReader(fh)
    game_fields = dict_reader.fieldnames[1:]  # 'name' कॉलम छोड़ें
    for rec in dict_reader:
        person = rec['name']
        scorebook[person] = {g: rec[g] for g in game_fields}
# scorebook:
# {
#   'James':  {'Game_1': '3', 'Game_2': '7', 'Game_3': '4'},
#   'Charles':{'Game_1': '2', 'Game_2': '3', 'Game_3': '8'},
#   'Bob':    {'Game_1': '6', 'Game_2': '2', 'Game_3': '4'}
# }

अगर आप प्रति गेम dict की list पसंद करते हैं, तो यह करें:

import csv
scorebook = {}
with open("scores.txt") as fh:
    dict_reader = csv.DictReader(fh)
    game_fields = dict_reader.fieldnames[1:]
    for rec in dict_reader:
        person = rec['name']
        scorebook[person] = [{g: rec[g]} for g in game_fields]
# scorebook:
# {
#   'James':  [ {'Game_1': '3'}, {'Game_2': '7'}, {'Game_3': '4'} ],
#   'Charles':[ {'Game_1': '2'}, {'Game_2': '3'}, {'Game_3': '8'} ],
#   'Bob':    [ {'Game_1': '6'}, {'Game_2': '2'}, {'Game_3': '4'} ]
# }

एक कॉम्पैक्ट तरीका ये भी है कि नाम वाली कुंजी हटाकर बाकी सब वैसा ही रख दें। इससे गेम कॉलम कितने भी हों और उनके नाम बदलते रहें, कोड अपने आप ढल जाता है:

import csv
scorebook = {}
with open("scores.txt") as fh:
    dict_reader = csv.DictReader(fh)
    for rec in dict_reader:
        person = rec.pop('name')  # कुंजी 'name' निकालें और हटा दें
        scorebook[person] = rec   # बची हुई कुंजियाँ गेम हैं
# संरचना एक-डिक्शनरी-प्रति-खिलाड़ी वाले वैरिएंट जैसी ही है

ये त्रुटियाँ क्यों आईं

KeyError: 0 और KeyError: 1 इसलिए आते हैं क्योंकि DictReader कॉलम नामों से की गई कुंजियों वाली dict लौटाता है। rec[0] या rec[1] जैसा इंडेक्स-आधारित एक्सेस इस मैपिंग में होता ही नहीं। मान पाने के लिए हेडर नाम इस्तेमाल करें, जैसे rec['name'] या rec['Game_1']।

ValueError: too many values to unpack (expected 2) तब आता है जब आप for left, right in dict_reader लिखते हैं। रीडर हर इटरेशन में एक dict देता है, जो कोई जोड़ी नहीं होती। Python फिर एक ही dict को दो वेरिएबल में अनपैक करने की कोशिश करता है, और यह विफल हो जाता है। पंक्तियों पर एक-एक करके इटरेट करें और कुंजियों को स्पष्ट रूप से एक्सेस करें।

यह क्यों मायने रखता है

गेम कॉलम और उनकी संख्या बदल सकती है। संख्यात्मक पोज़िशन की बजाय हेडर नामों पर आधारित ट्रांसफॉर्मेशन बनाना कोड को इन बदलावों के प्रति मज़बूत बनाता है। साथ ही इरादा साफ रहता है: आप खिलाड़ी के नाम को उसके स्कोर से मैप कर रहे हैं, न कि ऐसे इंडेक्स सँभाल रहे हैं जो CSV स्कीमा बदलते ही चुपचाप फिसल सकते हैं।

ऐसे ट्रांसफॉर्मेशन डिबग करते समय हर चरण पर अपने पास क्या है, यह देखने के लिए बीच-बीच में प्रिंट करना मदद करता है। पंक्ति ऑब्जेक्ट और उसका type प्रिंट करने से तुरंत साफ हो जाता है कि आप list के साथ काम कर रहे हैं या dict के साथ, और गलत मानसिक मॉडल के पीछे भागने से बचते हैं।

मुख्य बातें

कोड लिखने से पहले लक्ष्य संरचना तय करें, फिर csv.DictReader को मैपिंग चलाने दें। पंक्ति के मानों को rec['name'] जैसे हेडर नामों से एक्सेस करें, इंडेक्स से नहीं। अगर आपको प्रति खिलाड़ी एक ही dict चाहिए, तो गेम कॉलम के साथ dict comprehension इस्तेमाल करें या name कुंजी हटाकर बाकी रख दें। अगर आपको प्रति गेम dict की list या tuple चाहिए, तो उसे सीधे field names से बनाएं। इन पैटर्न पर टिके रहने से KeyError जैसी सरप्राइज नहीं मिलेंगी और कोड बदलते CSV हेडर के अनुकूल बना रहेगा।

यह लेख StackOverflow पर प्रश्न (लेखक: user31290254) और furas के उत्तर पर आधारित है।