फ़ाइलें पढ़ना और लिखना
with open के साथ फ़ाइलें पढ़ना और लिखना, r/w/a/x मोड का अंतर, प्रत्येक पंक्ति के अंत में न्यूलाइन, encoding हमेशा क्यों लिखना चाहिए, और छोटे कार्यों के लिए pathlib।
आप कैसे सीखना चाहते हैं?
वह समस्या जिसे हम हल कर रहे हैं
बाईस अध्यायों में हमने जो कुछ बनाया, वह सब प्रोग्राम ख़त्म होते ही मिट गया।
basket = []
basket.append("pen")
print(basket)['pen']अगली बार चलाएँ तो टोकरी फिर ख़ाली। लिस्ट, डिक्शनरी, फ़ंक्शन — सब कुछ मेमोरी में रहता है, और मेमोरी सिर्फ़ प्रोग्राम की उम्र तक रहती है।
पिछले अध्याय में हमने सीखा कि कोड को कई प्रोग्रामों के बीच कैसे बाँटा जाए। यह अध्याय डेटा के बारे में है — एक प्रोग्राम का काम अगली बार के लिए, या किसी और के लिए, कैसे छोड़ा जाए।
with open("notes.txt", "w", encoding="utf-8") as fh:
fh.write("first line\n")
fh.write("second line\n")
with open("notes.txt", "r", encoding="utf-8") as fh:
print(fh.read(), end="")first line
second lineप्रोग्राम ख़त्म हो गया, लेकिन notes.txt बची रही।
इस अध्याय के अंत तक आप कर पाएंगे
with open(...)से फ़ाइल पढ़ना और लिखना, और बताना किwithक्यों"r","w","a"और"x"का फ़र्क़ बताना — ख़ासकर यह कि"w"क्या मिटा देता है- फ़ाइल को एक-एक लाइन पढ़ना, और लाइन के आख़िर के
\nको सँभालना - बताना कि
encoding="utf-8"हमेशा क्यों लिखना चाहिए pathlib.Pathसे छोटे काम एक लाइन में निपटानाFileNotFoundErrorऔरUnicodeDecodeErrorपढ़कर समझना
पहले से क्या आना चाहिए: मॉड्यूल और इम्पोर्ट।
with क्यों
फ़ाइल खोलने का मतलब है ऑपरेटिंग सिस्टम से कुछ उधार लेना, और उधार चुकाना पड़ता है। फ़ाइल बंद न करें तो लिखा हुआ डेटा शायद डिस्क तक न पहुँचे, और बहुत सारी खुली फ़ाइलें जमा हो जाएँ तो प्रोग्राम रुक जाता है।
with ब्लॉक यह उधार ख़ुद चुकाता है — ब्लॉक ख़त्म होते ही फ़ाइल बंद, चाहे अंदर कोई एरर ही क्यों न आ जाए।
with open("notes.txt", "r", encoding="utf-8") as fh:
text = fh.read()
print(fh.closed)
print(fh.read())True
ValueError: I/O operation on closed file.(एरर से पहले पायथन traceback की कुछ लाइनें भी प्रिंट करता है; इस अध्याय में सिर्फ़ आख़िरी लाइन दिखाई गई है।)
fh नाम ब्लॉक के बाद भी है — स्कोप बिल्कुल पहले जैसा काम कर रहा है — लेकिन फ़ाइल बंद है। इस कोर्स में open() हमेशा with के साथ ही लिखा जाएगा।
पढ़ना
फ़ाइल के अंदर जो कुछ है, वह पूरा एक साथ .read() से मिलता है:
with open("notes.txt", "r", encoding="utf-8") as fh:
text = fh.read()
print(repr(text))
print(len(text))'first line\nsecond line\n'
23repr जानबूझकर इस्तेमाल किया गया है, क्योंकि आम print लाइन-ब्रेक छिपा देता है। फ़ाइल एक लंबी स्ट्रिंग है, और लाइनों के आख़िर उसके अंदर \n बनकर बैठे हैं — यह याद रखना इस अध्याय का आधा हिस्सा है।
एक-एक लाइन
बड़ी फ़ाइल के लिए .read() अच्छा नहीं, क्योंकि वह पूरी फ़ाइल को मेमोरी में ले आता है। फ़ाइल पर सीधे लूप चलाएँ तो पायथन एक-एक लाइन पढ़ता है:
with open("orders.txt", "r", encoding="utf-8") as fh:
for line in fh:
print(repr(line))'pen,15.0,3\n'
'bag,850.0,1\n'
'ink,120.0,2\n'हर लाइन के आख़िर में \n है। यही सबसे आम उलझन है — line == "pen" मेल नहीं खाता, डिक्शनरी की key के रूप में "pen\n" और "pen" अलग हैं, और वजह आँखों से दिखती नहीं। (int() और float() नंबर के आसपास के स्पेस और \n को ख़ुद ही नज़रअंदाज़ कर देते हैं, इसलिए int("3\n") ठीक 3 देता है — लेकिन स्ट्रिंग जैसी है वैसी ही रहती है, इसलिए तुलना और प्रिंट में \n रह जाता है।)
इसलिए लगभग हमेशा पहला काम है .strip():
with open("orders.txt", "r", encoding="utf-8") as fh:
for line in fh:
name, price, qty = line.strip().split(",")
print(f"{name:<5} {float(price) * int(qty):>8.2f}")pen 45.00
bag 850.00
ink 240.00.strip() फिर .split(",") फिर float() और int() — यह क्रम फ़ाइल पढ़ने के काम में बार-बार लौटेगा। फ़ाइल से जो आता है सब स्ट्रिंग है, नंबर नहीं; अध्याय 7 वाला कन्वर्ज़न यहाँ भी चाहिए।
सारी लाइनें एक साथ चाहिए तो दो तरीके हैं:
with open("orders.txt", "r", encoding="utf-8") as fh:
lines = fh.read().splitlines()
print(lines)['pen,15.0,3', 'bag,850.0,1', 'ink,120.0,2'].splitlines() लाइनों के आख़िर हटा देता है। .readlines() नाम का एक और है, लेकिन वह \n रहने देता है — और ज़्यादातर लोग वहीं ठोकर खाते हैं।
लिखना, और एक ख़तरनाक अक्षर
with open("notes.txt", "w", encoding="utf-8") as fh:
fh.write("new\n")फ़ाइल में पहले old content लिखा था। अब:
new"w" फ़ाइल को ख़ाली कर देता है — खोलते ही, कुछ लिखने से पहले ही। कोई चेतावनी नहीं, और वापस जाने का कोई तरीका भी नहीं।
पहले वाला रखकर आख़िर में जोड़ना हो तो "a":
with open("notes.txt", "a", encoding="utf-8") as fh:
fh.write("new\n")old content
newऔर अगर फ़ाइल पहले से हो तो बिल्कुल न लिखना चाहें, तो "x":
FileExistsError: [Errno 17] File exists: 'notes.txt'चारों मोड एक जगह: "r" पढ़ता है, "w" मिटाकर लिखता है, "a" आख़िर में जोड़ता है, "x" सिर्फ़ नई फ़ाइल बनाता है।
"w"लिखने से पहले एक बार रुकें। इस एक अक्षर की ग़लती से लोग असली काम खो देते हैं। जो फ़ाइल खोल रहे हैं वह अगर आपके प्रोग्राम की बनाई नहीं है, तो"w"लिखने से पहले सोचें।
\n आपको देना पड़ता है
print अपने-आप लाइन तोड़ता है। write नहीं तोड़ता।
rows = ["pen", "bag"]
with open("out.txt", "w", encoding="utf-8") as fh:
for row in rows:
fh.write(row)'penbag'इसलिए लाइनों की लिस्ट लिखने का सबसे साफ़ तरीका:
rows = ["pen", "bag"]
with open("out.txt", "w", encoding="utf-8") as fh:
fh.write("\n".join(rows) + "\n")'pen\nbag\n'"\n".join(rows) लाइनों के बीच ब्रेक लगाता है, और आख़िर का + "\n" फ़ाइल को एक नई लाइन पर ख़त्म करता है — टेक्स्ट फ़ाइलों का यही आम नियम है, और बहुत से टूल यही उम्मीद करते हैं।
encoding="utf-8" — हमेशा
encoding न लिखें तो पायथन ऑपरेटिंग सिस्टम का डिफ़ॉल्ट इस्तेमाल करता है, जो एक मशीन पर कुछ और दूसरी पर कुछ और होता है। नतीजा: ऐसा प्रोग्राम जो आपके यहाँ चलता है और किसी और के यहाँ टूट जाता है।
ग़लत एन्कोडिंग में पढ़ें तो यह आता है:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byteइसलिए जब भी open() लिखें, encoding="utf-8" लिखें — पढ़ने में भी और लिखने में भी। अपवाद सिर्फ़ बाइनरी मोड ("rb", "wb") है, जहाँ कोई एन्कोडिंग होती ही नहीं, इसलिए सवाल ही नहीं उठता।
pathlib — छोटे काम का छोटा रास्ता
छोटी फ़ाइल के लिए पूरा with ब्लॉक ज़्यादा लग सकता है।
from pathlib import Path
path = Path("orders.txt")
print(path.exists())
print(len(path.read_text(encoding="utf-8").splitlines()))
out = Path("report.txt")
out.write_text("done\n", encoding="utf-8")
print(out.read_text(encoding="utf-8"), end="")True
3
doneread_text और write_text अंदर ख़ुद ही फ़ाइल खोलते और बंद करते हैं, इसलिए with की ज़रूरत नहीं। Path के और भी काम हैं — .exists(), .name, .suffix, .parent — और फ़ोल्डर का रास्ता / से जुड़ता है, जैसे Path("data") / "orders.txt"।
कब कौन-सा? पूरी फ़ाइल एक साथ चाहिए तो Path, एक-एक लाइन पढ़नी हो तो with open(...)। एक बात याद रखें: write_text हमेशा "w" की तरह काम करता है — फ़ाइल पहले से हो तो उसका पुराना कंटेंट बिना किसी चेतावनी के मिट जाता है। उसमें आख़िर में जोड़ने का कोई तरीका नहीं; उसके लिए with open(..., "a") चाहिए।
एक पूरा उदाहरण
orders.txt:
pen,15.0,3
bag,850.0,1
ink,120.0,2main.py:
"""Read an order file, total it, and write a report beside it."""
from pathlib import Path
TAX_RATE = 0.15
def read_order(path):
"""Each line is name,price,quantity. Blank lines are skipped."""
lines = []
for raw in path.read_text(encoding="utf-8").splitlines():
if not raw.strip():
continue
name, price, quantity = raw.split(",")
lines.append((name, float(price), int(quantity)))
return lines
def report(lines):
rows = []
total = 0.0
for name, price, quantity in lines:
amount = round(price * quantity * (1 + TAX_RATE), 2)
total += amount
rows.append(f"{name:<6} {quantity:>3} {amount:>9.2f}")
rows.append("-" * 20)
rows.append(f"{'total':<6} {'':>3} {total:>9.2f}")
return rows
def main():
order = read_order(Path("orders.txt"))
rows = report(order)
Path("report.txt").write_text("\n".join(rows) + "\n", encoding="utf-8")
print(Path("report.txt").read_text(encoding="utf-8"), end="")
print()
print("lines read:", len(order))
if __name__ == "__main__":
main()pen 3 51.75
bag 1 977.50
ink 2 276.00
--------------------
total 1305.25
lines read: 3चार चीज़ें देखने लायक हैं।
तीन फ़ंक्शन, तीन अलग काम। read_order डिस्क से पढ़ता है, report सिर्फ़ हिसाब करता है, और main लिखता है। report को कोई फ़ाइल नहीं दी गई — उसे एक लिस्ट दी गई है, और वह एक और लिस्ट लौटाता है। इसलिए उसे टेस्ट करने के लिए कोई फ़ाइल नहीं चाहिए, और अध्याय 28 में ठीक यही बात काम आएगी।
ख़ाली लाइनें पहले से सँभाली गई हैं। आख़िर के एक \n से कोई ख़ाली लाइन नहीं बनती — .splitlines() उसे हटा देता है। लेकिन हाथ से एडिट की गई फ़ाइल में बीच में या आख़िर में एक असली ख़ाली लाइन (लगातार दो \n) होना बिल्कुल आम है, और उसे न सँभालें तो raw.split(",") को तीन चीज़ें नहीं मिलतीं और ValueError आता। if not raw.strip(): continue — एक लाइन में ख़त्म।
float(price) और int(quantity) के बिना काम नहीं चलेगा, क्योंकि फ़ाइल से सब स्ट्रिंग बनकर आता है। इन्हें भूल जाएँ तो price * quantity चुपचाप स्ट्रिंग को बार-बार जोड़ देता — और यही सबसे बुरी तरह का बग है, जो टूटता नहीं।
रिपोर्ट अलग फ़ाइल में जाती है। orders.txt कभी "w" से नहीं खोली गई — इनपुट फ़ाइल सुरक्षित रहती है। यह आदत डालने लायक है: जिसे पढ़ रहे हैं उसमें न लिखें।
जब कुछ टूट जाए
FileNotFoundError: [Errno 2] No such file or directory: 'orders.txt' फ़ाइल है ही नहीं, या आप प्रोग्राम किसी और फ़ोल्डर से चला रहे हैं। रास्ता जहाँ से चला रहे हैं वहाँ से गिना जाता है, स्क्रिप्ट के फ़ोल्डर से नहीं। Path("orders.txt").resolve() प्रिंट करके देखें कि वह कहाँ ढूँढ रहा है।
UnicodeDecodeError: 'utf-8' codec can't decode byte ... फ़ाइल किसी और एन्कोडिंग में लिखी है, या वह असल में टेक्स्ट फ़ाइल ही नहीं है। encoding ठीक करें, या बाइनरी हो तो "rb" से खोलें।
ValueError: I/O operation on closed file. with ब्लॉक के बाहर जाकर फ़ाइल इस्तेमाल की गई है। जो चाहिए वह ब्लॉक के अंदर ही पढ़ लें।
ValueError: not enough values to unpack किसी लाइन में उम्मीद के मुताबिक़ कॉमा नहीं हैं — अक्सर वह एक ख़ाली लाइन होती है। ख़ाली लाइनें छोड़ दें, और शक हो तो print(repr(raw)) से देखें।
फ़ाइल लिखी, लेकिन वह ख़ाली है with ब्लॉक ख़त्म होने से पहले ही फ़ाइल पढ़ी जा रही है। लिखा हुआ डेटा फ़ाइल बंद होते समय डिस्क तक पहुँचता है।
सारी लाइनें एक ही लाइन में जुड़ गईं write अपने-आप लाइन नहीं तोड़ता। "\n" आपको देना होगा।
मेरी फ़ाइल का सारा डेटा ग़ायब फ़ाइल "w" से खोली गई थी। पढ़ने के लिए "r", आख़िर में जोड़ने के लिए "a"।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
repr इस्तेमाल किया गया है, इसलिए कुछ भी छिपा नहीं है। क्या प्रिंट होगा?
# orders.txt
# pen,15.0,3
# bag,850.0,1
# ink,120.0,2
with open("orders.txt", "r", encoding="utf-8") as fh:
for line in fh:
print(repr(line))- A'pen,15.0,3' 'bag,850.0,1' 'ink,120.0,2'
- B'pen,15.0,3\nbag,850.0,1\nink,120.0,2\n'
- C'pen,15.0,3\n' 'bag,850.0,1\n' 'ink,120.0,2\n'
- D['pen,15.0,3', 'bag,850.0,1', 'ink,120.0,2']
फ़ाइल "w" से खोली गई, लेकिन कुछ लिखा नहीं गया। क्या प्रिंट होगा?
# notes.txt already contains: important work
with open("notes.txt", "w", encoding="utf-8") as fh:
print("opened")
with open("notes.txt", "r", encoding="utf-8") as fh:
print(repr(fh.read()))- Aopened 'opened\n'
- Bएक `FileExistsError`
- Copened 'important work\n'
- Dopened ''
दो चीज़ें अलग-अलग लिखी गई हैं। फ़ाइल में क्या है?
rows = ["pen", "bag"]
with open("out.txt", "w", encoding="utf-8") as fh:
for row in rows:
fh.write(row)
with open("out.txt", "r", encoding="utf-8") as fh:
print(repr(fh.read()))- A'pen\nbag\n'
- B'pen\nbag'
- C'pen bag'
- D'penbag'
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
people.txt नाम की एक फ़ाइल बनाएँ, हर लाइन में एक नाम और एक नंबर, कॉमा से अलग — कम से कम चार लाइनें, और जानबूझकर बीच में एक ख़ाली लाइन रखें।
फिर summary.py लिखें, जिसमें तीन फ़ंक्शन हों: एक फ़ाइल पढ़कर जोड़ों की लिस्ट बनाएगा, एक उस लिस्ट से लाइनों की लिस्ट बनाएगा (सबसे बड़ा नंबर किसका है, और कुल कितना), और main उसे summary.txt में लिखेगा। if __name__ == "__main__": रखना न भूलें।
फिर पाँच प्रयोग:
.strip()हटा दें, फिर हर लाइन का आख़िरी हिस्साrepr()से प्रिंट करें। क्याint()एरर देता है? (नहीं देता —int("10\n")ठीक10देता है।) तो\nकहाँ छिपा है, और ख़ाली लाइन पर क्या होता है?- किसी एक लाइन का नंबर मिटा दें, ताकि कॉमा रहे लेकिन नंबर न रहे। मैसेज पढ़ें।
mainमें"w"की जगह"a"से लिखें, फिर प्रोग्राम तीन बार चलाएँ।summary.txtका क्या हुआ?people.txtको"w"से खोलने की कोशिश करें... न करें। इसके बजाय एक कॉपी बनाकर उस पर करें, और देखें क्या बचता है।Path("people.txt").resolve()प्रिंट करें, फिर किसी दूसरे फ़ोल्डर से प्रोग्राम चलाएँ। क्या रास्ता बदलता है?
आख़िरी प्रयोग एक ऐसी ग़लती समझाता है जो लगभग हर कोई एक बार करता है: प्रोग्राम चल रहा था, फिर किसी और जगह से चलाते ही FileNotFoundError।
चरण 6 / 6
चुनौती — अध्याय की क्विज़
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
क्विज़ देने के लिए साइन इन करें