ফাইল পড়া আর লেখা
with open দিয়ে ফাইল পড়া ও লেখা, r/w/a/x মোডের পার্থক্য, প্রতিটি লাইনের শেষের নতুন-লাইন, encoding কেন সবসময় লিখতে হয়, আর pathlib দিয়ে ছোট কাজ।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
তেইশটা অধ্যায় ধরে আমরা যা কিছু বানিয়েছি, সবই প্রোগ্রাম শেষ হওয়ার সাথে সাথে মুছে গেছে।
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.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 আছে। এটাই সবচেয়ে সাধারণ বিভ্রান্তি — int(line) কাজ করে না, line == "pen" মেলে না, আর কারণটা চোখে দেখা যায় না।
তাই প্রায় সবসময় প্রথম কাজটা হলো .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() — এই ক্রমটা ফাইল পড়ার কাজে বারবার ফিরে আসবে। ফাইল থেকে যা আসে সবই লেখা, সংখ্যা নয়; সপ্তম অধ্যায়ের সেই রূপান্তরটা এখানেও লাগে।
সবগুলো লাইন একসাথে দরকার হলে দুটো উপায়:
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" টাইপ করার ভুলটা অন্তত আকস্মিক হয় না।
একটা সম্পূর্ণ উদাহরণ
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-কে কোনো ফাইল দেওয়া হয়নি — তাকে দেওয়া হয়েছে একটা লিস্ট, আর সে ফেরত দেয় আরেকটা লিস্ট। তাই তাকে পরীক্ষা করতে কোনো ফাইল লাগে না, আর অষ্টাবিংশ অধ্যায়ে গিয়ে আমরা ঠিক এই কারণেই তাকে ধন্যবাদ দেব।
খালি লাইনটা আগে থেকেই সামলানো। ফাইলের শেষে একটা বাড়তি নতুন লাইন থাকা খুবই স্বাভাবিক, আর সেটা না সামলালে 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"।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
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\n' 'bag,850.0,1\n' 'ink,120.0,2\n'
- B'pen,15.0,3' 'bag,850.0,1' 'ink,120.0,2'
- C'pen,15.0,3\nbag,850.0,1\nink,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 ''
- Bopened 'important work\n'
- Copened 'opened\n'
- Dএকটি `FileExistsError`
দুটো জিনিস আলাদা করে লেখা হয়েছে। ফাইলে কী আছে?
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'penbag'
- B'pen\nbag\n'
- C'pen bag'
- D'pen\nbag'
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
একটা ফাইল বানান people.txt, প্রতি লাইনে একটা নাম আর একটা সংখ্যা, কমা দিয়ে আলাদা — অন্তত চারটে লাইন, আর ইচ্ছে করে মাঝখানে একটা খালি লাইন রাখুন।
তারপর summary.py লিখুন, যেখানে তিনটে ফাংশন থাকবে: একটা ফাইল পড়ে জোড়ার লিস্ট বানাবে, একটা সেই লিস্ট থেকে লাইনের তালিকা বানাবে (সবচেয়ে বড় সংখ্যাটা কার, আর মোট কত), আর main সেটা summary.txt-এ লিখবে। if __name__ == "__main__": রাখতে ভুলবেন না।
তারপর পাঁচটা পরীক্ষা:
.strip()সরিয়ে দিন। কোন ত্রুটিটা আসে, আর কেন?- একটা লাইনের সংখ্যাটা মুছে দিন, যাতে কমা থাকে কিন্তু সংখ্যা না থাকে। বার্তাটা পড়ুন।
main-এ"w"-এর বদলে"a"দিয়ে লিখুন, তারপর প্রোগ্রামটা তিনবার চালান।summary.txt-এ কী হলো?people.txt-কে"w"দিয়ে খোলার চেষ্টা করুন... করবেন না। বরং একটা কপি বানিয়ে তার উপর করুন, আর দেখুন কী থাকে।Path("people.txt").resolve()ছাপুন, তারপর অন্য একটা ফোল্ডার থেকে প্রোগ্রামটা চালান। পথটা কি বদলায়?
শেষ পরীক্ষাটা এমন একটা ভুল ব্যাখ্যা করে যা প্রায় সবাই একবার করে: প্রোগ্রামটা কাজ করছিল, তারপর অন্য জায়গা থেকে চালাতেই FileNotFoundError।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন