पायथन में फ़ाइल प्रबंधन (File Handling)

प्रोग्राम जो कुछ चरों में रखता है, वह उसके समाप्त होते ही मिट जाता है। डेटा — अंक, रिकॉर्ड, लॉग — सहेजने के लिए प्रोग्राम उसे द्वितीयक भंडारण पर एक फ़ाइल में लिखता है और बाद में वापस पढ़ता है। यह अध्याय पायथन द्वारा प्रयुक्त दो प्रकार की फ़ाइलों को; open() और उसके एक्सेस मोड कैसे तय करते हैं कि फ़ाइल के साथ क्या किया जा सकता है और शुरुआत कहाँ से होगी; लिखने व पढ़ने की विधियों को; फ़ाइल पॉइंटर कैसे खिसकता है और tell() व seek() से कैसे खिसकाया जाता है; तथा pickle से बाइनरी फ़ाइलों में पूरे पायथन ऑब्जेक्ट और CSV फ़ाइलों में सारणीबद्ध डेटा कैसे सहेजा जाता है — इन सबको लेता है। पेपर मोड और पॉइंटर की स्थिति को बारीकी से परखता है, अतः ठीक-ठीक जानें कि प्रत्येक मोड मौजूदा सामग्री के साथ क्या करता है।

टेक्स्ट फ़ाइलें और बाइनरी फ़ाइलें

फ़ाइल द्वितीयक भंडारण पर एक नामित स्थान है। हर फ़ाइल बाइट्स (0 और 1) के रूप में सहेजी जाती है, पर उन बाइट्स को कैसे पढ़ा जाना है, इससे फ़ाइलें दो प्रकार की होती हैं। टेक्स्ट फ़ाइल में मनुष्य द्वारा पढ़े जा सकने वाले वर्ण होते हैं, हर बाइट या बाइट-समूह ASCII या Unicode में एक वर्ण को दर्शाता है; इसकी सामग्री पंक्तियों का अनुक्रम है, प्रत्येक एक पंक्ति-अंत (EOL) वर्ण पर समाप्त — पायथन में न्यूलाइन \n। सोर्स कोड, .txt और .csv फ़ाइलें टेक्स्ट फ़ाइलें हैं और टेक्स्ट एडिटर में ठीक से खुलती हैं। बाइनरी फ़ाइल में ऐसे बाइट्स होते हैं जो पाठ-वर्णों के अलावा कुछ और दर्शाते हैं — चित्र, ऑडियो, संकलित प्रोग्राम, या pickle से सहेजा गया पायथन ऑब्जेक्ट। टेक्स्ट एडिटर में यह अर्थहीन चिह्न दिखाती है, और एक भी ग़लत बाइट पूरी फ़ाइल को अपठनीय बना सकता है।

फ़ाइल खोलना और बंद करना

फ़ाइल file_object = open(file_name, access_mode) से खोली जाती है, जो एक फ़ाइल ऑब्जेक्ट (जिसे फ़ाइल हैंडल भी कहते हैं) लौटाता है, जिसके माध्यम से सारा पढ़ना-लिखना होता है। एक्सेस मोड वैकल्पिक है और डिफ़ॉल्ट "r", केवल-पठन टेक्स्ट, होता है। फ़ाइल ऑब्जेक्ट के तीन गुण हैं: file.closed (बंद होने पर True), file.mode और file.name। फ़ाइल file_object.close() से बंद होती है, जो लिखे जाने की प्रतीक्षा कर रहा सब कुछ फ़ाइल में भेज देता है। with कथन अपना खंड समाप्त होते ही फ़ाइल को स्वतः बंद कर देता है, भीतर अपवाद आ जाए तब भी: with open("myfile.txt", "r") as f:।

एक्सेस मोड — बाइनरी फ़ाइल के लिए b जोड़ें (rb, wb, ab, rb+ …)
मोडअनुमतिफ़ाइल मौजूद हो तोमौजूद न हो तोपॉइंटर कहाँ से
rकेवल पढ़नासामग्री सुरक्षितत्रुटि (FileNotFoundError)आरंभ
r+पढ़ना और लिखनासामग्री सुरक्षितत्रुटिआरंभ
wकेवल लिखनासामग्री मिटा दी जाती हैबनाई जाती हैआरंभ
w+लिखना और पढ़नासामग्री मिटा दी जाती हैबनाई जाती हैआरंभ
aकेवल जोड़ना (लिखना)सामग्री सुरक्षितबनाई जाती हैअंत
a+जोड़ना और पढ़नासामग्री सुरक्षितबनाई जाती हैअंत
⚠️ "w" विनाशकारी है
किसी मौजूदा फ़ाइल को "w" या "w+" मोड में खोलते ही वह तुरंत ख़ाली हो जाती है, एक भी वर्ण लिखने से पहले। जो है उसे खोए बिना फ़ाइल में जोड़ना हो तो "a" प्रयोग करें। "r+" भी सामग्री सुरक्षित रखता है, पर आरंभ से लिखता है और पॉइंटर के नीचे की सामग्री पर ऊपर से लिख देता है।

टेक्स्ट फ़ाइल में लिखना और उससे पढ़ना

  • write(string) एक स्ट्रिंग लिखता है और लिखे गए वर्णों की संख्या लौटाता है। यह अपनी ओर से कोई न्यूलाइन नहीं जोड़ता, और संख्या को पहले str() से बदलना पड़ता है।
  • writelines(sequence) किसी सूची या टपल की हर स्ट्रिंग लिखता है, यह भी बिना न्यूलाइन जोड़े, और None लौटाता है। f.writelines(["a", "b", "c"]) abc लिखता है।
  • read(n) n वर्ण पढ़ता है; बिना तर्क का read() फ़ाइल का शेष भाग एक स्ट्रिंग के रूप में पढ़ता है।
  • readline(n) एक पंक्ति पढ़ता है, उसके \n सहित, या उसके अधिकतम n वर्ण; फ़ाइल के अंत पर यह ख़ाली स्ट्रिंग लौटाता है।
  • readlines() सभी शेष पंक्तियाँ पढ़कर उन्हें स्ट्रिंगों की सूची के रूप में लौटाता है, प्रत्येक अब भी \n पर समाप्त। फ़ाइल ऑब्जेक्ट पर सीधे लूप भी चलाया जा सकता है: for line in f:।

notes.txt नामक फ़ाइल में दो पंक्तियाँ Hello और World हों, प्रत्येक के बाद न्यूलाइन, तो open("notes.txt").readlines() ['Hello\n', 'World\n'] लौटाता है — दो मदें, प्रत्येक अपनी न्यूलाइन सहित। ऐसी सूची छापते या तुलना करते समय \n भूल जाना आम चूक है।

फ़ाइल पॉइंटर: tell() और seek()

पायथन एक फ़ाइल पॉइंटर (ऑफ़सेट) रखता है जो बताता है कि अगला पठन या लेखन कहाँ होगा; हर पठन या लेखन उसे उतना आगे खिसका देता है जितना पढ़ा या लिखा गया। tell() वर्तमान स्थिति पूर्णांक के रूप में लौटाता है — आरंभ से बाइट्स की संख्या। seek(offset, reference_point) उसे खिसकाता है: reference_point 0 का अर्थ आरंभ से (डिफ़ॉल्ट), 1 वर्तमान स्थिति से और 2 अंत से। टेक्स्ट फ़ाइल में शून्य के अलावा ऑफ़सेट केवल आरंभ से प्रयोग होते हैं; सापेक्ष खिसकाव बाइनरी फ़ाइलों के लिए हैं।

हल किया गया उदाहरण। word.txt में केवल एक शब्द PROGRAMMING है। स्थितियाँ 0 से गिनी जाती हैं, अतः seek(3) के बाद पॉइंटर G पर है, read(4) GRAM लौटाता है, और फिर tell() 7 देता है:
f = open("word.txt")
f.seek(3)
print(f.read(4))
print(f.tell())

pickle से बाइनरी फ़ाइलें, और CSV फ़ाइलें

कोई सूची, डिक्शनरी या अन्य पायथन ऑब्जेक्ट फ़ाइल में ज्यों-का-त्यों नहीं लिखा जा सकता; पहले उसे बाइट्स की धारा में बदलना पड़ता है। यह रूपांतरण क्रमबद्धीकरण (serialization / pickling) है, और बाइट्स को वापस ऑब्जेक्ट बनाना विक्रमबद्धीकरण (de-serialization / unpickling)। pickle मॉड्यूल दोनों करता है। pickle.dump(data_object, file_object) बाइनरी लेखन मोड ("wb" या "ab") में खुली फ़ाइल में एक ऑब्जेक्ट लिखता है; pickle.load(file_object) "rb" में खुली फ़ाइल से एक ऑब्जेक्ट वापस पढ़ता है। load का हर कॉल अगला ऑब्जेक्ट लौटाता है, और अंतिम ऑब्जेक्ट के बाद load करने पर EOFError उठता है — इसलिए कई रिकॉर्ड वाली फ़ाइलें ऐसे लूप में पढ़ी जाती हैं जो उस अपवाद पर रुकता है।

मानक पठन-लूप हर रिकॉर्ड गिनता है और अंत पर साफ़ रुकता है:
import pickle
count = 0
with open("rec.dat", "rb") as f:
while True:
try:
obj = pickle.load(f)
count += 1
except EOFError:
break

CSV (कॉमा से अलग किए मान) फ़ाइल एक टेक्स्ट फ़ाइल है जिसकी हर पंक्ति एक रिकॉर्ड है और क्षेत्र कॉमा से अलग हैं — वह रूप जिसमें स्प्रेडशीट निर्यात करती हैं। csv मॉड्यूल इसे पढ़ता-लिखता है। csv.writer(f) एक writer ऑब्जेक्ट देता है जिसका writerow(list) एक रिकॉर्ड और writerows(list_of_lists) कई रिकॉर्ड लिखता है; csv.reader(f) ऐसा ऑब्जेक्ट देता है जो हर पंक्ति को स्ट्रिंगों की सूची के रूप में देता है, संख्याएँ भी स्ट्रिंग। फ़ाइल newline="" के साथ खोली जाती है ताकि पंक्ति-अंत मॉड्यूल नियंत्रित करे और ख़ाली पंक्तियाँ न आएँ।

⚠️ csv.reader स्ट्रिंग देता है
पंक्ति 101,Asha,88 ['101', 'Asha', '88'] के रूप में लौटती है, [101, 'Asha', 88] के रूप में नहीं। अंक जोड़ने हों तो पहले int() से बदलना होगा; स्ट्रिंगों को जोड़ने पर वे आपस में जुड़ जाएँगी।

मुख्य बिंदु

  • टेक्स्ट फ़ाइलों में \n पर समाप्त पंक्तियों में वर्ण होते हैं; बाइनरी फ़ाइलों में ऐसे बाइट्स होते हैं जो पाठ नहीं हैं, जैसे चित्र या pickle किए ऑब्जेक्ट।
  • open() का डिफ़ॉल्ट "r" है; "r" व "r+" को मौजूदा फ़ाइल चाहिए, "w" व "w+" उसे मिटा देते हैं, "a" व "a+" उसे सुरक्षित रख अंत से शुरू करते हैं।
  • write() लिखे गए वर्णों की संख्या लौटाता है; न write() न writelines() कोई न्यूलाइन जोड़ता है।
  • read(n) n वर्ण पढ़ता है, readline() अपने \n सहित एक पंक्ति, readlines() पंक्तियों की सूची।
  • tell() पॉइंटर की स्थिति देता है; seek(offset, 0/1/2) उसे आरंभ, वर्तमान स्थिति या अंत से खिसकाता है।
  • pickle.dump() को "wb"/"ab", pickle.load() को "rb" चाहिए, और EOFError रिकॉर्डों का अंत बताता है; csv.reader हर पंक्ति को स्ट्रिंगों की सूची के रूप में देता है।

अभ्यास प्रश्न (8)

उत्तर खोलने से पहले प्रत्येक प्रश्न हल करें। हर व्याख्या सही विकल्प के साथ लुभावना गलत विकल्प भी बताती है, क्योंकि अंक वहीं जाते हैं।

  1. फ़ाइल word.txt में केवल पाठ PROGRAMMING है। यह कोड क्या छापता है?
    f = open("word.txt")
    f.seek(3)
    print(f.read(4))

    1. OGRA
    2. GRAMM
    3. GRAM
    4. RAMM
    उत्तर देखें

    उत्तर: C — GRAM

    स्थितियाँ 0 से शुरू होती हैं, अतः P, R, O स्थान 0 से 2 घेरते हैं और seek(3) पॉइंटर को G पर छोड़ता है; फिर read(4) चार वर्ण G, R, A, M लौटाता है। OGRA स्थितियों को 1 से गिनने से आता है, जो पॉइंटर को एक स्थान पहले रख देता है। GRAMM चार के स्थान पर पाँच वर्ण पढ़ता है, और RAMM एक स्थान बाद से शुरू होता है।
  2. किसी प्रोग्राम को एक मौजूदा लॉग फ़ाइल के अंत में नई पंक्तियाँ जोड़नी हैं, उसकी पहले की सामग्री को छेड़े बिना। फ़ाइल किस मोड में खोली जानी चाहिए?

    1. "a"
    2. "w"
    3. "r"
    4. "w+"
    उत्तर देखें

    उत्तर: A — "a"

    एपेंड मोड फ़ाइल की सामग्री सुरक्षित रखता है और पॉइंटर को अंत पर रखता है, अतः हर लेखन पुरानी सामग्री के बाद होता है। "w" और "w+" भ्रामक हैं: दोनों मौजूदा फ़ाइल को खोलते ही ख़ाली कर देते हैं, अतः लॉग खो जाता। "r" फ़ाइल को केवल पढ़ने के लिए खोलता है और लिखने का कोई भी प्रयास विफल होता।
  3. notes.txt में दो पंक्तियाँ हैं: Hello और World, प्रत्येक के बाद न्यूलाइन। print(open("notes.txt").readlines()) क्या दिखाता है?

    1. ['Hello', 'World']
    2. ['Hello\n', 'World\n']
    3. Hello\nWorld\n
    4. ['H', 'e', 'l', 'l', 'o']
    उत्तर देखें

    उत्तर: B — `['Hello\n', 'World\n']`

    readlines() हर पंक्ति के लिए एक स्ट्रिंग वाली सूची लौटाता है, और हर स्ट्रिंग अपनी पंक्ति समाप्त करने वाली न्यूलाइन रखती है, अतः सूची 'Hello\n' और 'World\n' दिखाती है। \n छोड़ देना सबसे आम चूक है — उसके लिए हर मद पर strip() लगाना पड़ता। दोनों पंक्तियों वाली एक स्ट्रिंग read() लौटाता है, और वर्णों की सूची किसी स्ट्रिंग पर list() से मिलती।
  4. निम्नलिखित कोड द्वारा क्या छापा जाता है?
    f = open("out.txt", "w")
    n = f.write("Hi\n")
    print(n)
    f.close()

    1. 2
    2. None
    3. True
    4. 3
    उत्तर देखें

    उत्तर: D — 3

    write() लिखे गए वर्णों की संख्या लौटाता है, और "Hi\n" तीन वर्ण है — H, i और न्यूलाइन। 2 उत्तर देना न्यूलाइन को अनदेखा करना है, जो टाइप चाहे जैसे हो, एक ही वर्ण है। None वह है जो writelines() लौटाता है, और write() कभी बूलियन नहीं लौटाता।
  5. निम्नलिखित में से कौन pickle का प्रयोग कर पायथन सूची L को बाइनरी फ़ाइल में सही ढंग से लिखता है?

    1. pickle.dump(L, open("d.dat", "w"))
    2. pickle.load(L, open("d.dat", "wb"))
    3. pickle.dump(L, open("d.dat", "wb"))
    4. pickle.dump(open("d.dat", "wb"), L)
    उत्तर देखें

    उत्तर: C — `pickle.dump(L, open("d.dat", "wb"))`

    pickle.dump पहले ऑब्जेक्ट और दूसरे फ़ाइल ऑब्जेक्ट लेता है, और pickle किया डेटा बाइट्स होता है, अतः फ़ाइल बाइनरी लेखन मोड "wb" में खुलनी चाहिए। साधारण "w" से टेक्स्ट फ़ाइल खुलती है, जो dump द्वारा बनाए बाइट्स को अस्वीकार करती है। load पढ़ने वाला फ़ंक्शन है और केवल फ़ाइल लेता है, और dump के दोनों तर्क आपस में बदलने से वस्तु के स्थान पर फ़ाइल चली जाती है।
  6. एक लूप pickle.load() से बाइनरी फ़ाइल के रिकॉर्ड तब तक पढ़ता है जब तक कोई शेष न रहे। कौन-सा अपवाद बताता है कि फ़ाइल का अंत आ गया है?

    1. IndexError
    2. EOFError
    3. FileNotFoundError
    4. StopIteration
    उत्तर देखें

    उत्तर: B — EOFError

    pickle.load() का हर कॉल अगला सहेजा ऑब्जेक्ट लौटाता है, और अंतिम के बाद किया गया कॉल कोई डेटा नहीं पाता और EOFError उठाता है, इसीलिए पठन-लूप try के भीतर रहता है और उसी अपवाद पर टूटता है। IndexError अनुक्रमों का है, फ़ाइलों का नहीं। FileNotFoundError किसी अनुपस्थित फ़ाइल को खोलते समय आता, उसके अंत पर नहीं, और StopIteration वह है जिसे for लूप भीतर प्रयोग करता है, न कि जिसे load उठाता है।
  7. अभिकथन (A): किसी मौजूदा फ़ाइल को "w" मोड में खोलकर तुरंत बंद करने पर फ़ाइल ख़ाली रह जाती है। कारण (R): "w" मोड में फ़ाइल पॉइंटर मौजूदा सामग्री के अंत पर रखा जाता है।

    1. A और R दोनों सत्य हैं, और R, A की सही व्याख्या है
    2. A और R दोनों सत्य हैं, पर R, A की सही व्याख्या नहीं है
    3. A सत्य है, पर R असत्य है
    4. A असत्य है, पर R सत्य है
    उत्तर देखें

    उत्तर: C — A सत्य है, पर R असत्य है

    फ़ाइल को "w" मोड में खोलते ही वह तुरंत छोटी (truncate) कर दी जाती है, अतः कुछ न लिखने पर भी पुरानी सामग्री चली जाती है और A सत्य है। R एपेंड मोड का वर्णन है: "w" में पॉइंटर अब ख़ाली हो चुकी फ़ाइल के आरंभ से शुरू होता है, अतः R असत्य है। "दोनों सत्य" चुनना w और a को उलझाना है, जो इस अध्याय का सबसे अधिक परखा जाने वाला भेद है।
  8. marks.csv में पंक्ति 101,Asha,88 है। फ़ाइल f के रूप में खोली जाती है और for row in csv.reader(f): print(row) से पढ़ी जाती है। उस पंक्ति के लिए क्या छपता है?

    1. [101, 'Asha', 88]
    2. 101,Asha,88
    3. ('101', 'Asha', '88')
    4. ['101', 'Asha', '88']
    उत्तर देखें

    उत्तर: D — `['101', 'Asha', '88']`

    csv.reader हर पंक्ति को कॉमा पर तोड़कर क्षेत्रों को सूची के रूप में देता है, और CSV फ़ाइल टेक्स्ट होने के कारण हर क्षेत्र स्ट्रिंग है, संख्याएँ भी। 101 और 88 को बिना उद्धरण वाली सूची मानती है कि मॉड्यूल संख्याएँ बदल देता है, जो वह कभी नहीं करता — अंकगणित से पहले int() चाहिए। कच्ची पंक्ति फ़ाइल को सीधे पढ़ने से मिलती, और reader सूचियाँ देता है, टपल नहीं।