এই পর্বে যা শিখবেন
- ফাইল কেন দরকার, আর text ফাইল কীভাবে “বাইট + encoding-এর চুক্তি”
open()-এর প্রতিটি মোড কী করে, আরwithকেন ফাইল খোলার সঠিক উপায়- পড়া-লেখার সব উপায় —
read,readline, লুপে লাইন ধরে পড়া,write,print(..., file=f) - বাংলা লেখায় encoding, mojibake, BOM, newline আর binary ফাইল টুকরো করে কপি
pathlibদিয়ে ফোল্ডার-ফাইল সামলানো, ফাইলের error ধরা, crash-এও নিরাপদ লেখা আর অবিশ্বস্ত path থেকে সাবধানতা
১ · ফাইল কেন — প্রোগ্রামের স্মৃতি মুছে যায়, ফাইল থেকে যায়
এতদিন আপনার প্রতিটি প্রোগ্রাম একটি অদ্ভুত স্মৃতিভ্রংশে ভুগেছে। পর্ব ২১-এর CLI টুলে আপনি ছাত্রদের নাম আর নম্বর যোগ করেছেন, dict-এ সুন্দর করে সাজিয়েছেন। তারপর প্রোগ্রাম বন্ধ করলেন — আর সব শেষ। পরের বার চালালে আবার খালি dict. কারণ ভ্যারিয়েবল, list, dict — সবই থাকে RAM-এ, আর RAM-এর জিনিস প্রোগ্রাম শেষ হলে (বা বিদ্যুৎ গেলে) মুছে যায়।
ডেটাকে প্রোগ্রামের আয়ুর বাইরেও টিকিয়ে রাখার নাম persistence. এর সবচেয়ে পুরোনো ও সরল উপায় হলো file — হার্ডডিস্ক, SSD বা পেনড্রাইভে নাম দিয়ে রাখা বাইটের একটি সারি। ডেটাবেস, ক্লাউড স্টোরেজ, এমনকি AI মডেলের বিশাল weight — শেষ পর্যন্ত সবই কোথাও না কোথাও ফাইল হিসেবেই থাকে। তাই ফাইল পড়া-লেখা প্রায় প্রতিটি বাস্তব প্রোগ্রামের ভিত্তি।
পাড়ার মুদি দোকানের কথা ভাবুন। দোকানদার সারাদিন মাথায় হিসাব রাখেন — কে বাকি নিল, আজ কত বিক্রি হলো। কিন্তু রাতে বাড়ি ফেরার আগে সব লিখে রাখেন হালখাতায়। মাথার হিসাব হলো RAM — দ্রুত, কিন্তু ঘুমালেই ঝাপসা। হালখাতা হলো ফাইল — ধীর, কিন্তু পরের দিন সকালে খুললেই সব আছে। আর খাতায় লেখার একটি নিয়মও আছে: দোকানদার বাংলায় লিখলে, পরের দিন যিনি পড়বেন তাঁকেও বাংলা পড়তে জানতে হবে। ফাইলের বেলায় এই “ভাষার চুক্তি”-র নাম encoding.
text ফাইল = বাইট + একটি চুক্তি
ডিস্ক শুধু বাইট চেনে — ০ থেকে ২৫৫-এর সংখ্যা। “বাংলা” শব্দটি ডিস্কে কোনো অক্ষর হিসেবে থাকে না; থাকে ১৫টি বাইট হিসেবে, কারণ পর্ব ০৯-এ দেখেছেন, UTF-8-এ প্রতিটি বাংলা code point ৩ বাইট। লেখার সময় Python আপনার str-কে encode করে বাইট বানায়; পড়ার সময় বাইটকে decode করে আবার str বানায়। লেখা আর পড়ার সময় একই encoding ব্যবহার হলে সব ঠিক। না মিললে পাবেন আবর্জনা অক্ষর বা UnicodeDecodeError.
ফাইল দুই রকম চোখে দেখা যায়। Text চোখে — ফাইল হলো লাইনে ভাগ করা লেখা, Python দেয় str, আর মাঝখানে encoding-এর অনুবাদ চলে। Binary চোখে — ফাইল হলো কাঁচা বাইট, Python দেয় bytes, কোনো অনুবাদ নেই। ছবি, PDF, zip, mp3 সবসময় binary চোখে; .txt, .py, .csv, .html সাধারণত text চোখে। Python 3 এই দুটিকে কঠোরভাবে আলাদা রাখে — ভুল চোখে দেখলে সঙ্গে সঙ্গে error.
Python-এ ফাইলের সাথে সব কাজ হয় একটি file object-এর মাধ্যমে। open() ফাংশন অপারেটিং সিস্টেমের (OS) কাছে ফাইলটি চায়; OS একটি ছোট নম্বর দেয়, যাকে বলে file descriptor; Python সেটি মুড়ে দেয় একটি সুবিধাজনক object-এ, যার আছে read(), write(), close() ইত্যাদি method. এই object আসলে একটি stream — এক মাথা আপনার প্রোগ্রামে, আরেক মাথা ডিস্কে। পর্ব ১০-এ দেখা print() আর input()-ও আসলে এমন stream-এ লেখে-পড়ে: sys.stdout আর sys.stdin হলো ঠিক একই জাতের file object.
২ · open() আর মোড — r, w, a, x, b, +
open(path, mode, encoding=...) — প্রথমে ফাইলের নাম বা path, তারপর mode: আপনি ফাইলটি দিয়ে কী করতে চান। mode না দিলে ডিফল্ট "r" — শুধু পড়া, text হিসেবে। মোডগুলো ছোট অক্ষর, কিন্তু প্রতিটির পরিণাম বড়:
| মোড | কাজ | ফাইল না থাকলে | ফাইল আগে থেকে থাকলে |
|---|---|---|---|
"r" | পড়া (ডিফল্ট) | FileNotFoundError | শুরু থেকে পড়ে |
"w" | লেখা | নতুন বানায় | সব লেখা মুছে ফেলে, খালি থেকে শুরু |
"a" | শেষে যোগ (append) | নতুন বানায় | পুরোনো লেখা রেখে শেষে যোগ করে |
"x" | একচেটিয়া নতুন ফাইল | নতুন বানায় | FileExistsError — কিছুই ছোঁয় না |
"b" (যোগ করে) | binary: "rb", "wb", "ab" | উপরের নিয়মই; শুধু str-এর বদলে bytes | |
"+" (যোগ করে) | পড়া ও লেখা দুটোই: "r+", "w+", "a+" | "r+" মোছে না; "w+" মুছে ফেলে | |
সবচেয়ে বিপজ্জনক অক্ষরটি হলো "w". এটি ফাইল খোলার মুহূর্তেই ফাইলটিকে শূন্য বাইটে নামিয়ে আনে — একে বলে truncate করা। আপনি একটি অক্ষরও না লিখলেও পুরোনো সব লেখা ততক্ষণে চলে গেছে। আর "x" মোড এসেছে Python 3.3-এ, ঠিক এই ভয় থেকে: “নতুন ফাইল বানাও, কিন্তু আগে থেকে থাকলে ভুলেও ছুঁয়ো না।” একটি ফাইল যার আগে থেকে দুটি লাইন আছে, তার ওপর তিনটি মোডের ফল ধাপে ধাপে দেখুন:
শনিবার,১২৫০ রবিবার,৯৮০
(শনিবার আর রবিবার — open-এর মুহূর্তেই মুছে গেছে) সোমবার,১৪২০
শনিবার,১২৫০
রবিবার,৯৮০
সোমবার,১৪২০
FileExistsError: [Errno 17] File exists: 'sales.txt' ফাইলে যা ছিল, তাই আছে: শনিবার,১২৫০ রবিবার,৯৮০
| মোড | |
| লাইন সংখ্যা | |
| পুরোনো লেখা |
- শুরুর ফাইল।
sales.txt-এ শনি আর রবিবারের বিক্রি। পরের তিনটি ধাপের প্রতিটি এই একই শুরুর অবস্থা থেকে আলাদাভাবে চালানো হয়েছে ধরে নিন। "w"— নতুন খাতা।open()ফেরত আসার আগেই ফাইল ০ বাইট। তারপর শুধু সোমবারের লাইনটি লেখা হলো। “শেষে যোগ করতে চেয়েছিলাম” ভেবে"w"লিখলে দুই দিনের হিসাব নিঃশব্দে হারিয়ে যায় — কোনো error, কোনো সতর্কতা নেই।"a"— পুরোনো খাতার শেষ পাতায়। লেখার অবস্থান সবসময় ফাইলের শেষে। প্রতিদিনের বিক্রির ডায়েরি, লগ ফাইল, SMS-এর রেকর্ড — যেখানে ইতিহাস জমতে থাকে, সেখানে এটাই সঠিক মোড।"x"— শুধু নতুন হলে। ফাইল আগে থেকে আছে, তাই PythonFileExistsErrorতোলে এবং ফাইলটি ছোঁয়ই না। রিপোর্ট বা ব্যাকআপ বানানোর সময় ভুল করে আগের দিনেরটি মুছে ফেলার বিরুদ্ধে এটি সস্তা বিমা।
সবচেয়ে প্রচলিত ফাইল-বাগ: ডায়েরি বা লগে নতুন লাইন যোগ করতে "w" ব্যবহার। প্রোগ্রাম চলে, কোনো error আসে না, প্রতিবার শুধু শেষ লাইনটি থাকে — আর আপনি টের পান কয়েক দিন পরে। নিয়ম মনে রাখুন: ইতিহাস জমাতে "a", পুরো ফাইল নতুন করে বানাতে "w", ভুলেও পুরোনোটি না মুছতে "x".
"+" মোডগুলো ("r+" ইত্যাদি) একই ফাইলে পড়া আর লেখা দুটোই দেয়, কিন্তু তখন “এখন ফাইলের কোন জায়গায় আছি” — এই অবস্থান (f.seek(), f.tell()) নিজে সামলাতে হয়। text ফাইলে এটি ঝামেলার, কারণ বাংলা অক্ষর কয়েক বাইটের — ভুল জায়গায় লিখলে অক্ষর ভেঙে যায়। বাস্তবে প্রায় সবসময় সহজ পথটিই ভালো: পুরো ফাইল পড়ুন, মেমরিতে বদলান, তারপর নতুন করে লিখুন (অংশ ৮-এর নিরাপদ উপায়ে)।
৩ · with — ফাইল খোলার সঠিক উপায়
প্রতিটি খোলা ফাইল OS-এর একটি সম্পদ দখল করে রাখে। আর আপনি যা write() করেন, তা সঙ্গে সঙ্গে ডিস্কে যায় না — প্রথমে জমা হয় মেমরির একটি buffer-এ, কারণ ডিস্কে বারবার অল্প অল্প লেখা খুব ধীর। buffer ভরে গেলে, বা ফাইল বন্ধ হলে, জমা লেখা ডিস্কে যায় (একে বলে flush). তাই ফাইল বন্ধ না করলে দুটি বিপদ: শেষ অংশের লেখা ডিস্কে না পৌঁছাতে পারে, আর অনেক ফাইল খোলা রেখে দিলে OS-এর সীমা ফুরিয়ে যেতে পারে। Windows-এ খোলা ফাইল অন্য প্রোগ্রাম মুছতে বা সরাতেও পারে না।
পুরোনো ধাঁচে লেখা হতো f = open(...), কাজ, তারপর f.close(). সমস্যা হলো মাঝখানে কোনো exception উঠলে close() লাইনে আর পৌঁছানোই হয় না। সমাধান হলো with statement:
with open("sms_log.txt", "a", encoding="utf-8") as f:
f.write("বিকাশ: ৫০০ টাকা এসেছে\n")
# এখানে এসে f নিশ্চিতভাবে বন্ধ — error হোক বা না হোকwith ব্লক যেভাবেই শেষ হোক — স্বাভাবিকভাবে, return দিয়ে, বা exception উঠে — ফাইল বন্ধ হবেই। কেন এটা কাজ করে? কারণ file object একটি context manager: সে জানে ব্লকে ঢোকার সময় কী করতে হবে আর বের হওয়ার সময় কী। ভেতরের যন্ত্রপাতি (__enter__, __exit__) আর নিজের context manager বানানো দেখবেন পর্ব ৩৬-এ; এখন শুধু মনে রাখুন — ফাইল মানেই with. একটি error হলে ঠিক কী ঘটে, ধাপে ধাপে দেখুন:
1with open("sms_log.txt", "a", encoding="utf-8") as f:2 f.write("বিকাশ: ৫০০ টাকা এসেছে\n")3 amount = int("৫০০x") # ভুল ডেটা4print("লগ লেখা শেষ")
| f.closed | |
| buffer | |
| ডিস্কে ফাইল |
- খোলা।
open()OS-এর কাছ থেকে ফাইলটি নিল, file object বানাল, আরas fসেটিকেfনামে দিল।"a"মোড, তাই লেখার জায়গা ফাইলের শেষে। - লেখা — কিন্তু আসলে buffer-এ।
write()লেখাটিকে UTF-8 বাইটে encode করে মেমরির buffer-এ রাখল। ডিস্ক এখনো আগের মতো। এই মুহূর্তে বিদ্যুৎ গেলে লাইনটি হারাত। - Exception.
int("৫০০x")একটিValueErrorতুলল। স্বাভাবিক নিয়মে Python এখন সব থামিয়ে বাইরের দিকে ছুটবে — পুরোনো ধাঁচেরf.close()থাকলে সেটি আর চলত না। withবের হওয়ার আগে বন্ধ করে। ব্লক ছাড়ার আগেwithfile object-কে বন্ধ করতে বলে। বন্ধ করার সময় buffer flush হয় — নতুন লাইনটি এবার ডিস্কে পৌঁছাল, আর OS-এর সম্পদ ফেরত গেল।- Exception চলতে থাকে।
witherror গিলে ফেলে না — ফাইল বন্ধ করার পর সেটি আবার উপরে ওঠে, তাই লাইন ৪ চলে না আর traceback দেখা যায়। error ধরে সামলাতে চাইলেtry/exceptলাগবে (পর্ব ২৪), যা অংশ ৮-এ দেখবেন।
CPython তো নিজেই বন্ধ করে, তাই না? হ্যাঁ, CPython-এ file object-কে আর কেউ না দেখালে reference counting তাকে প্রায় সঙ্গে সঙ্গে মুছে দেয়, আর মোছার সময় ফাইলও বন্ধ হয়। কিন্তু এটি নিশ্চয়তা নয় — PyPy-র মতো অন্য implementation-এ তা অনেক পরে হতে পারে, exception-এর traceback object ফাইলটিকে বাঁচিয়ে রাখতে পারে, আর Python বন্ধ না-করা ফাইলের জন্য ResourceWarning দিতে পারে (ডিফল্টে লুকানো থাকে)। “কখন বন্ধ হবে” — এটা ভাগ্যের ওপর না ছেড়ে with দিয়ে স্পষ্ট করে দিন।
একসাথে দুটি ফাইল খুলতে একটি with-এই কমা দিয়ে লেখা যায়: with open("in.txt", encoding="utf-8") as src, open("out.txt", "w", encoding="utf-8") as dst:. লাইন লম্বা হলে Python 3.10 থেকে পুরোটা বন্ধনী দিয়ে কয়েক লাইনে ভাঙা যায়।
৪ · পড়া আর লেখা — পুরোটা, লাইন ধরে, না এক এক করে
ফাইল খোলার পর পড়ার চারটি উপায় আছে। কোনটি বেছে নেবেন, তা নির্ভর করে ফাইল কত বড় আর আপনি কী করতে চান তার ওপর:
| উপায় | ফেরত দেয় | কখন ভালো |
|---|---|---|
f.read() | পুরো ফাইল একটি str-এ | ছোট ফাইল, পুরো লেখায় একবারে খোঁজা |
f.readline() | পরের একটি লাইন (শেষে "\n"-সহ); শেষ হলে "" | হেডার লাইন আলাদা করে পড়া |
for line in f: | লুপের প্রতিবারে একটি লাইন | প্রায় সবসময় — বড় ফাইলেও মেমরি অল্প লাগে |
f.readlines() | সব লাইনের একটি list | কদাচিৎ — পুরো ফাইল মেমরিতে, কিন্তু লাইনে ভাগ করা |
file object নিজেই একটি iterator: লুপ প্রতিবার next() চাইলে সে ফাইল থেকে পরের লাইনটি পড়ে দেয়। ফলে ১০ GB-র একটি লগ ফাইলেও for line in f: একবারে মাত্র একটি লাইন (আর একটি buffer) মেমরিতে রাখে। অথচ f.read() বা f.readlines() পুরো ১০ GB RAM-এ তোলার চেষ্টা করবে — আপনার ল্যাপটপ থমকে যাবে। iterator কীভাবে কাজ করে, পর্ব ৩৪-এ গভীরে দেখবেন।
একটি জিনিস খেয়াল রাখুন: পড়া প্রতিটি লাইনের শেষে newline অক্ষর "\n" থেকে যায় (শেষ লাইনে না-ও থাকতে পারে)। তাই লাইন নিয়ে কাজের আগে সাধারণত line.rstrip("\n") বা line.strip() লাগে। strip() দুই পাশের সব ফাঁকা অক্ষর মোছে — লাইনের শুরুর ফাঁকা জায়গার অর্থ থাকলে শুধু rstrip("\n") ব্যবহার করুন। আর একবার পুরো ফাইল পড়া হয়ে গেলে file object “শেষে” থাকে — আবার read() ডাকলে পাবেন খালি string, যদি না ফাইল নতুন করে খোলেন।
লেখার তিন উপায়
f.write(s)— একটি string লেখে, যা দিয়েছেন হুবহু তাই। নিজে থেকে নতুন লাইন যোগ করে না, তাই"\n"নিজে লিখতে হয়। ফেরত দেয় কয়টি অক্ষর লেখা হলো।print(..., file=f)—print()-এর সব সুবিধা (একাধিক মান,sep, শেষে নিজে থেকে"\n", সংখ্যা নিজে থেকে লেখায় রূপান্তর) কিন্তু পর্দার বদলে ফাইলে।f.writelines(lines)— string-এর একটি list বা যেকোনো iterable একের পর এক লেখে। নামে “lines” থাকলেও এটি নিজে থেকে"\n"বসায় না!
এবার সব একসাথে — একটি দোকানের প্রতিদিনের বিক্রির ডায়েরি। প্রথম দিন "w" দিয়ে নতুন খাতা, পরের দিনগুলো "a" দিয়ে যোগ, তারপর লাইন ধরে পড়ে মোট হিসাব, আর শেষে "x"-এর পাহারা পরীক্ষা:
from pathlib import Path
diary = Path("sales.txt")
with open(diary, "w", encoding="utf-8") as f: # নতুন খাতা — পুরোনো লেখা মুছে যায়
f.write("শনিবার,১২৫০\n")
with open(diary, "a", encoding="utf-8") as f: # শেষে যোগ হয়
print("রবিবার,৯৮০", file=f)
f.writelines(["সোমবার,১৪২০\n"])
total = 0
with open(diary, encoding="utf-8") as f:
for line in f: # এক লাইন করে পড়া
day, amount = line.rstrip("\n").split(",")
total += int(amount)
print(day, int(amount))
print("মোট:", total, "টাকা")
try:
with open(diary, "x", encoding="utf-8") as f:
f.write("নতুন")
except FileExistsError:
print("x মোড: ফাইল আগেই আছে, কিছু লেখা হয়নি")
print(diary.stat().st_size, "বাইট,", len(diary.read_text(encoding="utf-8")), "অক্ষর")আউটপুট: তিন দিনের লাইন (শনিবার 1250, রবিবার 980, সোমবার 1420), তারপর মোট: 3650 টাকা, তারপর x মোডের বার্তা, আর শেষে 93 বাইট, 35 অক্ষর. কয়েকটি জিনিস লক্ষ করুন। print(..., file=f) নিজে থেকে "\n" বসিয়েছে, কিন্তু write আর writelines-এ তা নিজে লিখতে হয়েছে। int("১২৫০") বাংলা অঙ্ক সরাসরি 1250 বানিয়েছে (পর্ব ০৯-এ দেখেছেন)। "x" মোড ফাইল ছোঁয়নি, শুধু FileExistsError তুলেছে, যা আমরা try/except দিয়ে ধরেছি। আর শেষ লাইন encoding-এর প্রমাণ: ৩৫টি অক্ষর ডিস্কে নিয়েছে ৯৩ বাইট — প্রতিটি বাংলা অক্ষর ৩ বাইট, কমা আর newline ১ বাইট করে। (নিজের Windows কম্পিউটারে চালালে দেখবেন ৯৬ বাইট, কারণ সেখানে প্রতিটি newline ডিস্কে "
" — ২ বাইট; অংশ ৫-এর newline-এর অনুবাদ দেখুন। অক্ষর তবু ৩৫টিই।) এই কোডটি নিজের working directory-তে sales.txt বানায়; বারবার চালালেও ফল একই, কারণ প্রথম "w" প্রতিবার খাতা নতুন করে শুরু করে।
আরও দুটি বাস্তব উদাহরণ
দোকানের দামের তালিকা থেকে dict. ধরুন prices.txt-এ প্রতিটি লাইনে চাল=75 ধরনের লেখা। লাইন ধরে পড়ে, ফাঁকা লাইন ও # দিয়ে শুরু মন্তব্য বাদ দিয়ে একটি dict বানানো যায়:
price = {}
with open("prices.txt", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
name, taka = line.split("=", 1)
price[name.strip()] = int(taka)বাংলা লেখায় শব্দ গোনা. একটি গল্প বা খবরের ফাইলে কোন শব্দ কতবার এসেছে — NLP-র একেবারে প্রথম ধাপ। লাইন ধরে পড়ুন, split() দিয়ে শব্দে ভাঙুন, দাঁড়ি-কমা ছেঁটে dict-এ গুনুন। বাংলায় একটি সতর্কতা: strip(".,!?")-এ বাংলা দাঁড়ি । যোগ করতে ভুলবেন না, আর অনুশীলন ২-এ পুরো সমাধান দেখবেন। collections.Counter দিয়ে এটি আরও ছোট হয় (পর্ব ২৭)।
৫ · Encoding — বাইট থেকে বাংলা, আর বাংলা থেকে আবর্জনা
অংশ ১-এর চুক্তির কথা মনে করুন: text ফাইল মানে বাইট + encoding. open()-এ encoding না বললে Python একটি ডিফল্ট ধরে নেয় — আর এই ডিফল্টটাই বহু বছর ধরে বাংলা (এবং সব অ-ইংরেজি) ব্যবহারকারীর মাথাব্যথা। ডিস্কের বাইট কীভাবে str হয়, আর ভুল encoding-এ কী ঘটে, ধাপে ধাপে দেখুন। ফাইলে আছে শুধু চা আর একটি newline:
e0 a6 9a e0 a6 be 0a
↑ শুধু সংখ্যা। কোনো “অক্ষর” এখানে নেই।
[e0 a6 9a] → U+099A চ [e0 a6 be] → U+09BE া [0a] → U+000A \n e0 দিয়ে শুরু মানে: “আমি ৩ বাইটের একটি দলের প্রথম বাইট”
f.read() → 'চা\n'
f.read().strip() → 'চা' len = 2
e0 → à a6 → ¦ 9a → š e0 → à a6 → ¦ be → ¾ 'চা' ← mojibake: প্রতিটি বাইটকে আলাদা অক্ষর ভাবা হয়েছে
encoding="ascii" → UnicodeDecodeError: 'ascii' codec can't decode byte 0xe0 ... অসম্পূর্ণ বাইট [e0 a6] + errors="replace" → '�' সমাধান একটাই: যে encoding-এ লেখা, সেটিতেই পড়ুন — encoding="utf-8"
| encoding | |
| ফল |
- ডিস্কে শুধু বাইট। হেক্সাডেসিমালে ৭টি বাইট। এগুলো “চা” কি না, তা বাইট নিজে বলে না — বলে encoding-এর চুক্তি।
- UTF-8-এর নিয়মে ভাগ। UTF-8 প্রথম বাইট দেখে বোঝে দলটি কত বাইটের:
e0মানে ৩ বাইটের দল। প্রতিটি দল থেকে একটি code point — চ (U+099A), আ-কার (U+09BE), আর newline. - সঠিক
str.read()দেয়'চা\n'; newline ছাঁটলে'চা', যারlen2. এটাই আপনি চেয়েছিলেন। - ভুল encoding — নিঃশব্দ আবর্জনা। Windows-এর পুরোনো ডিফল্ট cp1252 প্রতিটি বাইটকে একটি আলাদা ইউরোপীয় অক্ষর ভাবে। কোনো error নেই, প্রোগ্রাম চলছে — কিন্তু লেখা হয়ে গেছে
চা. এই জঞ্জালের নাম mojibake. আরও খারাপ: এই ভুল লেখা আবার ফাইলে লিখলে আসল ডেটাই নষ্ট। - কঠোর বা ক্ষমাশীল।
ascii-এর মতো কঠোর codec সোজাUnicodeDecodeErrorদেয় — অন্তত ভুলটা চোখে পড়ে।errors="replace"ভাঙা বাইটের জায়গায়�বসিয়ে এগিয়ে যায়। কিন্তু কোনোটিই আসল সমাধান নয়; সমাধান হলো লেখা ও পড়া দুই জায়গাতেইencoding="utf-8".
ডিফল্ট encoding-এর ইতিহাস — সাবধানে বলি
open()-এ encoding না দিলে Python ব্যবহার করে আপনার কম্পিউটারের locale-এর encoding. Linux আর macOS-এ এটি প্রায় সবসময় UTF-8, তাই সেখানে encoding না লিখলেও বাংলা সাধারণত ঠিক থাকে। কিন্তু Windows-এ এটি ঐতিহাসিকভাবে একটি “ANSI code page” — ইংরেজি সেটিংয়ে সাধারণত cp1252, যা বাংলা লিখতেই পারে না। ফলাফল পরিচিত: আপনার ল্যাপটপে (Linux) কোড ঠিক চলে, বন্ধুর Windows-এ UnicodeEncodeError বা mojibake.
- Python 3.7 (PEP 540) আনে ঐচ্ছিক UTF-8 Mode:
python -X utf8বা পরিবেশ ভ্যারিয়েবলPYTHONUTF8=1দিলে locale যা-ই হোক, ডিফল্ট হয় UTF-8. - Python 3.10 (PEP 597) আনে
EncodingWarning—-X warn_default_encodingদিয়ে চালালে encoding ছাড়া প্রতিটিopen()-এর জন্য সতর্কতা দেখায়, আর locale-এর encoding ইচ্ছা করে চাইলে লেখা যায়encoding="locale". - PEP 686 সিদ্ধান্ত নিয়েছে UTF-8 Mode-কে ডিফল্ট করার, আর পরিকল্পনা অনুযায়ী এটি Python 3.15 থেকে চালু হওয়ার কথা। তখনও কেউ
PYTHONUTF8=0দিয়ে বন্ধ করতে পারবেন, আর লাখো মানুষ পুরোনো Python চালাতেই থাকবেন।
তাই ভবিষ্যতে ডিফল্ট যা-ই হোক, নিয়ম একটাই: text মোডের প্রতিটি open(), read_text(), write_text()-এ encoding="utf-8" লিখুন। এতে কোড যেকোনো কম্পিউটারে, যেকোনো Python সংস্করণে একইভাবে চলে, আর যিনি কোড পড়বেন তিনিও চুক্তিটি দেখতে পান। ruff-এর মতো linter-এ এর জন্য নিয়মও আছে (পর্ব ৪৪)।
BOM আর utf-8-sig
কিছু Windows প্রোগ্রাম UTF-8 ফাইলের একেবারে শুরুতে তিনটি বাড়তি বাইট বসায়: ef bb bf. একে বলে BOM (Byte Order Mark). Excel-এর “CSV UTF-8” ফরম্যাট এটা করে, আর Windows 10-এর ২০১৯ সালের আপডেটের আগে Notepad “UTF-8” বলতে BOM-সহ UTF-8 বুঝত। encoding="utf-8" দিয়ে পড়লে BOM একটি অদৃশ্য অক্ষর '\ufeff' হয়ে প্রথম লাইনের শুরুতে বসে থাকে। ফলে first_line == "নাম" হয় False, যদিও প্রিন্ট করলে দুটি হুবহু একই দেখায়! সমাধান: এমন ফাইল পড়ুন encoding="utf-8-sig" দিয়ে — BOM থাকলে বাদ দেয়, না থাকলে কিছুই করে না। নিজের ফাইল লেখার সময় সাধারণ "utf-8" (BOM ছাড়া) ব্যবহার করুন; শুধু যদি জানেন ফাইলটি Excel-এ খোলা হবে আর বাংলা ঠিক দেখাতে হবে, তখন "utf-8-sig" দিয়ে লিখুন। CSV-র বিস্তারিত পর্ব ২৬-এ।
Newline-এর অনুবাদ
লাইন শেষের চিহ্ন সব OS-এ এক নয়: Linux/macOS-এ "\n", Windows-এ "\r\n" (দুটি বাইট)। text মোডে Python এই পার্থক্য লুকিয়ে রাখে — একে বলে universal newlines. পড়ার সময় "\r\n" বা "\r" যা-ই থাকুক, আপনি পাবেন শুধু "\n". লেখার সময় আপনার "\n" বদলে যায় সেই OS-এর নিজস্ব চিহ্নে। এজন্যই Windows-এ একই লেখার ফাইল একটু বড় হয়। এই অনুবাদ থামাতে চাইলে open(..., newline="") — CSV module ঠিক এটাই চায় (পর্ব ২৬)। আর binary মোডে কোনো অনুবাদই হয় না: যা আছে, হুবহু তাই।
মোডের অক্ষরগুলো C-র fopen() থেকেই নেওয়া — "r", "w", "a", "rb", "r+", আর C11-এর "wx". পার্থক্য হলো, C-তে text আর binary মোডের তফাত শুধু Windows-এর newline অনুবাদে; Python-এ দুটি একেবারে আলাদা জগৎ (str বনাম bytes) আর encoding-এর অনুবাদও text মোডের কাজ। fclose() ভুলে যাওয়ার বদলে Python দেয় with, যা C++-এর std::ifstream-এর RAII-এর মতো ব্লক শেষে বন্ধ করে; আর pathlib হলো C++17-এর std::filesystem::path-এর জ্ঞাতি ভাই। গভীরে দেখতে C কোর্সের ফাইল I/O পর্ব আর C++-এর ফাইল I/O ও filesystem পর্ব দেখুন।
৬ · Binary মোড আর বড় ফাইল — ছবি, PDF, টুকরো করে কপি
ছবি, PDF, zip, ভিডিও — এগুলোর বাইট কোনো লেখা নয়। এগুলো "r" দিয়ে খুললে Python বাইটগুলোকে UTF-8 হিসেবে decode করার চেষ্টা করবে আর প্রায় সঙ্গে সঙ্গে UnicodeDecodeError দেবে। আর errors="replace" দিয়ে জোর করে পড়ে আবার লিখলে ফাইলটি স্থায়ীভাবে নষ্ট হয়ে যাবে। এমন ফাইলের জন্য binary mode — "rb", "wb". তখন read() দেয় bytes, write() চায় bytes, আর encoding দেওয়া যায় না (দিলে ValueError), কারণ অনুবাদ করার কিছুই নেই। এর উল্টো text mode, যা ডিফল্ট।
একটি বড় ভিডিও কপি করতে হলে পুরোটা read() করে RAM-এ তোলা বোকামি। এর বদলে নির্দিষ্ট মাপের টুকরো — chunk — পড়ুন আর লিখুন, যতক্ষণ না read() খালি b"" ফেরত দেয় (মানে ফাইলের শেষ, EOF). পর্ব ০৮-এর walrus অপারেটর এখানে চমৎকার মানায়:
CHUNK = 1024 * 1024 # একবারে ১ MB
with open("lecture.mp4", "rb") as src, open("backup.mp4", "wb") as dst:
while chunk := src.read(CHUNK):
dst.write(chunk)এই লুপ ৪ GB-র ফাইলেও মাত্র ১ MB মেমরি নেয়। বাস্তবে নিজে লুপ না লিখে standard library-র shutil.copyfile(src, dst) বা shutil.copy2() (metadata-সহ) ব্যবহার করুন — ভেতরে একই কাজ, আর কিছু OS-এ আরও দ্রুত কৌশলও কাজে লাগায়। কিন্তু টুকরো করে পড়ার ধারণাটি মনে রাখুন: ফাইল থেকে hash বের করা, নেটওয়ার্কে আপলোড, বড় ডেটাসেট প্রক্রিয়া — সব জায়গায় এই একই নকশা।
বড় text ফাইল → for line in f
বড় binary ফাইল → read(CHUNK) লুপে
binary মোডে বাইট দেখে ফাইলের ধরনও চেনা যায়। প্রতিটি PNG শুরু হয় \x89PNG দিয়ে, প্রতিটি PDF %PDF দিয়ে — এদের বলে “magic number”. open(p, "rb").read(4) পড়ে মিলিয়ে দেখলে বোঝা যায়, কেউ .exe ফাইলের নাম বদলে .pdf করে দিয়েছে কি না। ফাইলের extension শুধু একটি নাম; আসল পরিচয় ভেতরের বাইটে।
৭ · pathlib — path-কে string নয়, object ভাবুন
ফাইলের ঠিকানাকে বলে path. Windows-এ C:\Users\Rahim\Documents\sales.txt, Linux-এ /home/rahim/sales.txt. যে path মূল (root) থেকে শুরু, সেটি absolute path; আর data/sales.txt-এর মতো path, যা প্রোগ্রামের বর্তমান ফোল্ডার — working directory — থেকে গোনা হয়, সেটি relative path. খেয়াল করুন, relative path স্ক্রিপ্টের ফোল্ডার থেকে নয়, যে ফোল্ডার থেকে প্রোগ্রাম চালানো হয়েছে সেখান থেকে গোনা হয় — নতুনদের “ফাইল তো আছে, তবু FileNotFoundError কেন?” প্রশ্নের অর্ধেক উত্তর এটাই।
নতুনরা path বানায় string জোড়া দিয়ে: folder + "\\" + name. এতে সমস্যা অনেক — Windows-এর backslash Linux-এ চলে না, "\n"-এর মতো escape sequence গোলমাল করে (পর্ব ০৯), কোথাও স্ল্যাশ দুবার বা একবারও না বসার ভয়। Python 3.4 থেকে standard library-তে আছে pathlib, যেখানে path একটি object — আর / অপারেটর দিয়ে জোড়া লাগে, OS অনুযায়ী সঠিক বিভাজক নিজেই বসে:
from pathlib import Path
p = Path("shop") / "2026" / "report.final.pdf"
p.name # 'report.final.pdf'
p.stem # 'report.final' (শেষ extension বাদ)
p.suffix # '.pdf'
p.parent # Path('shop/2026')
p.with_suffix(".txt") # Path('shop/2026/report.final.txt')| কাজ | pathlib | পুরোনো ধাঁচ (os / os.path) |
|---|---|---|
| জোড়া লাগানো | base / "data" / name | os.path.join(base, "data", name) |
| আছে কি না | p.exists(), p.is_file(), p.is_dir() | os.path.exists(p), os.path.isfile(p) |
| ফোল্ডার বানানো | p.mkdir(parents=True, exist_ok=True) | os.makedirs(p, exist_ok=True) |
| পুরো লেখা পড়া/লেখা | p.read_text(encoding="utf-8"), p.write_text(s, encoding="utf-8") | with open(...) |
| ভেতরের সব জিনিস | p.iterdir() | os.listdir(p) |
| প্যাটার্নে খোঁজা | p.glob("*.pdf"), p.rglob("*.pdf") | glob.glob(...), os.walk(...) |
| মোছা / নাম বদল | p.unlink(), p.rename(q), p.replace(q) | os.remove(p), os.rename(p, q), os.replace(p, q) |
| পূর্ণ path | p.resolve() | os.path.abspath(p), os.path.realpath(p) |
পুরোনো কোড, টিউটোরিয়াল আর AI-এর উত্তরে আপনি প্রচুর os.path দেখবেন — সেগুলো ভুল নয়, শুধু পুরোনো ধাঁচ: path সেখানে সাধারণ string, আর প্রতিটি কাজ আলাদা ফাংশন। পড়তে পারা দরকার, নতুন কোডে pathlib পছন্দ করুন। দুটি মিশেও যায়: open(), os, shutil — সবাই Path object সরাসরি নেয়। mkdir()-এ parents=True মাঝের সব ফোল্ডারও বানায়, আর exist_ok=True ফোল্ডার আগে থাকলে error দেয় না — দুটি একসাথে দিলে “নিশ্চিত করো যে ফোল্ডারটি আছে” কাজটি এক লাইনে হয়।
glob প্যাটার্নে * মানে “যেকোনো কিছু”, ? মানে “যেকোনো একটি অক্ষর”: glob("*.txt") শুধু এই ফোল্ডারে খোঁজে, rglob("*.txt") ভেতরের সব ফোল্ডারেও। একটি গুরুত্বপূর্ণ সতর্কতা: iterdir(), glob() আর os.listdir() কোনো নির্দিষ্ট ক্রমে ফল দেয় না — ক্রম নির্ভর করে OS আর file system-এর ওপর। এক কম্পিউটারে যে ক্রম, আরেকটিতে ভিন্ন। তাই ফল দেখানো, রিপোর্ট বানানো বা টেস্টে সবসময় sorted(...) দিয়ে নিন।
এবার একটি বাস্তব কাজ: Downloads ফোল্ডার গোছানো — প্রতিটি ফাইলকে তার extension-এর নামের ফোল্ডারে সরানো। সাথে দামের তালিকা নিরাপদে লেখা, যা অংশ ৮-এ ব্যাখ্যা করা হবে:
from pathlib import Path
downloads = Path("downloads")
downloads.mkdir(exist_ok=True)
for name in ["bill.pdf", "photo.JPG", "notes.txt", "song.mp3", "cv.pdf"]:
(downloads / name).write_text("demo", encoding="utf-8")
for item in sorted(downloads.iterdir()): # sorted — ক্রম নিশ্চিত
if item.is_file():
folder = downloads / item.suffix.lower().lstrip(".")
folder.mkdir(parents=True, exist_ok=True)
item.rename(folder / item.name)
for p in sorted(downloads.rglob("*")):
if p.is_file():
print(p.relative_to(downloads).as_posix(), "|", p.stem, p.suffix)
prices, tmp = Path("prices.txt"), Path("prices.txt.tmp")
tmp.write_text("চাল=75\nডাল=140\n", encoding="utf-8")
tmp.replace(prices) # একবারে পুরো ফাইল বদল
lines = prices.read_text(encoding="utf-8").splitlines()
print(dict(ln.split("=") for ln in lines), prices.exists(), tmp.exists())আউটপুট পাঁচটি লাইন: jpg/photo.JPG | photo .JPG, mp3/song.mp3 | song .mp3, pdf/bill.pdf | bill .pdf, pdf/cv.pdf | cv .pdf, txt/notes.txt | notes .txt; তারপর {'চাল': '75', 'ডাল': '140'} True False. লক্ষ করুন: suffix.lower() দিয়েছি বলে .JPG গেছে jpg ফোল্ডারে, অথচ ফাইলের নিজের নাম বদলায়নি। sorted(downloads.iterdir()) আগে পুরো তালিকা বানিয়ে নেয়, তাই লুপের মধ্যে ফাইল সরালেও গোলমাল হয় না — যে ফোল্ডারের ওপর হাঁটছেন, সেটি হাঁটার মাঝখানে বদলানো সাধারণত বিপজ্জনক। relative_to() আর as_posix() দিয়েছি যাতে absolute path (যা প্রতিটি কম্পিউটারে আলাদা) প্রিন্ট না হয়, আর Windows-এও স্ল্যাশ একই দেখায়। শেষ অংশে temp ফাইলে লিখে replace(): prices.txt আছে (True), temp নেই (False), কারণ নাম বদলে সেটিই আসল ফাইল হয়েছে। বাস্তবে একটি সতর্কতা: আসল Downloads-এ এমন কোড চালানোর আগে নামের সংঘর্ষ ভাবুন — POSIX-এ rename() একই নামের পুরোনো ফাইল নিঃশব্দে মুছে দিতে পারে, Windows-এ FileExistsError দেয়।
read_text() আর write_text() কখন? ছোট ফাইলে পুরো লেখা একবারে চাইলে এগুলো সবচেয়ে সংক্ষিপ্ত — ভেতরে নিজেই with open করে বন্ধও করে দেয়। কিন্তু এরাও পুরো ফাইল মেমরিতে তোলে, তাই বড় লগ ফাইলে with open(...) আর for line in f-ই ঠিক। আর এদেরও encoding-এর ডিফল্ট locale — encoding="utf-8" এখানেও লিখতে হবে। binary-র জন্য আছে read_bytes() আর write_bytes(). মুছতে unlink() (ফাইল না থাকলে error এড়াতে unlink(missing_ok=True), Python 3.8 থেকে), আর খালি ফোল্ডার মুছতে rmdir().
৮ · ফাইলের error, crash-এ নিরাপদ লেখা আর অবিশ্বস্ত path
ফাইল আপনার প্রোগ্রামের বাইরের জগৎ — আর বাইরের জগৎ আপনার কথা শোনে না। ফাইল মুছে যেতে পারে, অনুমতি না থাকতে পারে, ডিস্ক ভরে যেতে পারে, পেনড্রাইভ খুলে নেওয়া হতে পারে। তাই ফাইলের কোডে error সামলানো ঐচ্ছিক নয়। সব ফাইল-সংক্রান্ত error হলো OSError-এর সন্তান (এই গাছটি Python 3.3-এ PEP 3151 দিয়ে গোছানো হয়েছিল), আর সবচেয়ে প্রচলিতগুলো:
| Error | কখন | সাধারণ সমাধান |
|---|---|---|
| FileNotFoundError | "r" মোডে নেই এমন ফাইল; বা লেখার সময় মাঝের ফোল্ডার নেই | বানান ও working directory যাচাই; ফোল্ডারে mkdir(parents=True, exist_ok=True) |
| PermissionError | অনুমতি নেই; Windows-এ অন্য প্রোগ্রাম (যেমন Excel) ফাইলটি খুলে রেখেছে | ব্যবহারকারীকে স্পষ্ট বার্তা; ফাইলটি বন্ধ করতে বলা |
| IsADirectoryError | ফাইলের জায়গায় ফোল্ডারের নাম দিয়ে open() (Windows-এ এর বদলে সাধারণত PermissionError আসে) | আগে is_file() যাচাই |
| FileExistsError | "x" মোডে বা mkdir()-এ নাম আগে থেকেই আছে | ইচ্ছাকৃত পাহারা; নতুন নাম বা exist_ok=True |
UnicodeDecodeError | ভুল encoding-এ পড়া (এটি ValueError-এর সন্তান, OSError-এর নয়) | সঠিক encoding; দরকারে "utf-8-sig" |
এগুলো ধরার কৌশল পর্ব ২৪-এর try/except. একটি গুরুত্বপূর্ণ নীতি: “আগে দেখো, তারপর খোলো” নয়, “খুলে দেখো, না হলে সামলাও”. if p.exists(): open(p) লিখলে, exists() আর open()-এর মাঝের মুহূর্তে অন্য কোনো প্রোগ্রাম ফাইলটি মুছে ফেলতে পারে (একে বলে race condition). তাই সরাসরি খুলুন, আর নির্দিষ্ট error ধরুন:
from pathlib import Path
def load_prices(path: Path) -> dict[str, int]:
try:
text = path.read_text(encoding="utf-8-sig")
except FileNotFoundError:
print(f"{path.name} পাওয়া যায়নি — খালি তালিকা দিয়ে শুরু করছি")
return {}
except PermissionError:
print(f"{path.name} খোলার অনুমতি নেই — Excel-এ খোলা থাকলে বন্ধ করুন")
raise
return {k: int(v) for k, v in (ln.split("=", 1) for ln in text.splitlines() if ln)}লক্ষ করুন, FileNotFoundError-এর ক্ষেত্রে আমরা একটি যুক্তিসংগত বিকল্প (খালি তালিকা) দিয়েছি, কিন্তু PermissionError-এ বার্তা দিয়ে আবার raise করেছি — কারণ সেখানে চুপচাপ এগিয়ে যাওয়ার কোনো ভালো উপায় নেই। except Exception: বা খালি except: দিয়ে সব গিলে ফেলবেন না; তাতে ডিস্ক ভরে যাওয়ার মতো আসল সমস্যাও লুকিয়ে যায়।
নিরাপদ লেখা — temp ফাইলে লিখে replace
ধরুন আপনার দোকানের ৩০০ পণ্যের দামের তালিকা prices.txt-এ। আপনি দাম আপডেট করে "w" দিয়ে নতুন করে লিখছেন, আর ঠিক অর্ধেক লেখার সময় লোডশেডিং। এখন ফাইলে নতুন তালিকার অর্ধেক — পুরোনোটিও নেই, নতুনটিও নেই। এই বিপদ এড়ানোর কৌশল হলো temp ফাইলে লেখো, তারপর এক ধাক্কায় বদলাও:
1tmp = path.with_name(path.name + ".tmp")2with open(tmp, "w", encoding="utf-8") as f:3 f.write(new_text)4 f.flush(); os.fsync(f.fileno())5os.replace(tmp, path)
| prices.txt | |
| prices.txt.tmp | |
| সরাসরি "w" দিলে prices.txt |
- temp-এর নাম। temp ফাইল রাখা হয় আসল ফাইলের একই ফোল্ডারে। কেন, তা ধাপ ৫-এ। সরাসরি
"w"-এর পথে (নিচের সারি) এখনো কিছু ঘটেনি। - খোলা। নিরাপদ পথে খোলা হলো temp; আসল
prices.txtঅক্ষত। কিন্তু সরাসরি"w"-এর পথে ফাইল খোলার মুহূর্তেই আসল তালিকা ০ বাইট — বিপদের জানালা খুলে গেল। - লেখা চলছে। দুই পথেই অর্ধেক লেখা হয়েছে। পার্থক্য হলো, নিরাপদ পথে এই অর্ধেক লেখা আছে temp-এ, আর পাশে পুরোনো তালিকা পুরোটা বসে আছে।
- লেখার মাঝখানে crash. বিদ্যুৎ গেল, প্রোগ্রাম মরল। সরাসরি
"w"-এর পথে দোকানের দামের তালিকা ভাঙা — পুরোনোটিও নেই। নিরাপদ পথে আসল ফাইল একদম অক্ষত; শুধু একটি অসম্পূর্ণ.tmpপড়ে আছে, যা পরের বার মুছে ফেললেই হলো। - সব ঠিক থাকলে
os.replace. লেখা শেষেflush()আরos.fsync()বাইটগুলোকে সত্যিই ডিস্কে পাঠায়, তারপরos.replace()(বাPath.replace()) temp-কে আসল নামে বসায়। একই file system-এ POSIX এই নাম বদলকে atomic হওয়ার নিশ্চয়তা দেয় — কেউ কখনো আধা-লেখা ফাইল দেখবে না। ভিন্ন ড্রাইভে (যেমন C: থেকে D:) নাম বদল সম্ভব নয়, তাই temp একই ফোল্ডারে।
বাস্তবে temp ফাইলের নাম নিজে বানানোর বদলে standard library-র tempfile.NamedTemporaryFile(dir=path.parent, delete=False) ব্যবহার করা ভালো, যাতে দুটি প্রোগ্রাম একই temp নাম না নেয়। আর fsync ধীর — প্রতিটি লগ লাইনে দরকার নেই; দরকার কনফিগারেশন, দামের তালিকা, সেভ-ফাইলের মতো “নষ্ট হলে সর্বনাশ” ফাইলে। Windows-এ os.replace() সাধারণত কাজ করে, কিন্তু POSIX-এর মতো কড়া atomic নিশ্চয়তা নথিতে দেওয়া নেই, আর আসল ফাইলটি অন্য প্রোগ্রাম খুলে রাখলে PermissionError আসতে পারে।
অবিশ্বস্ত path — ফোল্ডারের বাইরে যেতে দেবেন না
ধরুন আপনার ওয়েব অ্যাপে ব্যবহারকারী একটি ফাইলের নাম পাঠায়, আর আপনি Path("uploads") / name খুলে দেখান। কেউ যদি নাম পাঠায় ../../.env? .. মানে “এক ফোল্ডার উপরে” — তাই এই path uploads ছেড়ে বেরিয়ে আপনার গোপন পাসওয়ার্ডের ফাইল পর্যন্ত চলে যেতে পারে। এই আক্রমণের নাম path traversal. আর absolute path পাঠালে আরও সহজ: Path("uploads") / "/etc/passwd"-এর ফল শুধু /etc/passwd — / অপারেটর ডান দিকের absolute path পেলে বাম দিকটি ফেলে দেয়।
প্রতিরক্ষার মূল কথা (বিস্তারিত পর্ব ৫০-এ): বাইরে থেকে আসা নাম কখনো বিশ্বাস করবেন না। path-টি resolve() দিয়ে পূর্ণ, ..-মুক্ত আকারে আনুন, তারপর যাচাই করুন সেটি অনুমোদিত ফোল্ডারের ভেতরেই আছে কি না — target.is_relative_to(base) (Python 3.9 থেকে)। না হলে বাতিল। আরও ভালো হলো ব্যবহারকারীর দেওয়া নাম আদৌ ব্যবহার না করে নিজে একটি নিরাপদ নাম (যেমন একটি ID) দেওয়া। এটি input validation-এরই একটি রূপ।
"w"আর"a"-এর পার্থক্য? —"w"খোলার মুহূর্তেই ফাইল খালি করে নতুন করে লেখে;"a"পুরোনো লেখা রেখে শেষে যোগ করে। দুটিই ফাইল না থাকলে নতুন বানায়।- ফাইল খুলতে
withকেন? — ব্লক যেভাবেই শেষ হোক (exception-সহ), ফাইল নিশ্চিতভাবে বন্ধ হয় আর buffer flush হয়। - ৫ GB-র লগ ফাইল কীভাবে পড়বেন? —
for line in f:দিয়ে লাইন ধরে;read()বাreadlines()পুরো ফাইল মেমরিতে তোলে। - বাংলা লেখা
বাহয়ে গেলে কারণ কী? — UTF-8 ফাইল ভুল encoding (যেমন cp1252) দিয়ে পড়া হয়েছে; সমাধানencoding="utf-8". Path("a.tar.gz").suffixকী? —'.gz'; সব extension চাইলে.suffixes→['.tar', '.gz'].
৯ · AI-এর যুগে — ফাইলের কোডে AI-এর চেনা ভুল
“একটি ফাইল পড়ে এটা করো” — AI-কে দেওয়া সবচেয়ে সাধারণ অনুরোধগুলোর একটি, আর AI-এর কোড সাধারণত প্রথম চেষ্টায় চলেও যায়। সমস্যা হলো, AI-কে যে কম্পিউটারে পরীক্ষা করা হয়নি, তার কথা সে ভাবে না: Windows-এর ডিফল্ট encoding, ১০ GB-র ফাইল, লেখার মাঝখানে crash, বা ভুল করে মুছে যাওয়া পুরোনো ডেটা। নিচের ভুলগুলো বারবার দেখা যায়:
| AI প্রায়ই যা লেখে | সমস্যা | ভালো উপায় |
|---|---|---|
open("data.txt") — encoding নেই | Linux-এ চলে, Windows-এ বাংলায় mojibake বা error | encoding="utf-8" সবসময় |
f = open(...) … f.close() | মাঝে error হলে ফাইল খোলা থাকে, লেখা হারাতে পারে | with open(...) as f: |
data = f.read() বা f.readlines() | বড় ফাইলে পুরোটা RAM-এ | for line in f: |
folder + "\\" + name | Linux/macOS-এ ভাঙে, escape-এর গোলমাল | Path(folder) / name |
লগ বা ডায়েরিতে "w" | প্রতিবার আগের সব মুছে যায় | ইতিহাস জমাতে "a" |
os.listdir()-এর ফল সরাসরি ব্যবহার | ক্রম অনিশ্চিত — টেস্ট আর রিপোর্ট একেক সময় একেক রকম | sorted(p.iterdir()) |
জরুরি ফাইল সরাসরি "w"-তে লেখা | লেখার মাঝে crash হলে ফাইল ভাঙা | temp ফাইলে লিখে os.replace() |
| ব্যবহারকারীর দেওয়া নাম সরাসরি path-এ | path traversal (../) | resolve() করে অনুমোদিত ফোল্ডারের ভেতরে কি না যাচাই |
AI-কে ফাইলের কোড লিখতে বললে শুরুতেই পরিবেশ আর ঝুঁকিগুলো জানিয়ে দিন। নিচের কাঠামোটি কপি করে ব্যবহার করুন:
ফাইলের কাজে AI-এর কোড পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Unix-এর “সবকিছুই ফাইল” দর্শনটি কী? Python-এর print(), sys.stdin আর open() একই রকম কেন দেখতে?
১৯৬৯-৭০ সালে AT&T-র Bell Labs-এ Ken Thompson আর Dennis Ritchie যখন Unix বানাচ্ছিলেন, তাঁরা একটি সাহসী সরলীকরণ করেন: ডিস্কের ফাইল, কীবোর্ড, টার্মিনাল, প্রিন্টার, এমনকি দুটি প্রোগ্রামের মাঝের যোগাযোগের নল (pipe) — সবকিছুর সাথে কথা বলা হবে একই কয়েকটি system call দিয়ে: open, read, write, close. যন্ত্রগুলো দেখা দিত /dev ফোল্ডারের “ফাইল” হিসেবে। প্রতিটি খোলা জিনিস পেত একটি ছোট পূর্ণসংখ্যা — file descriptor — আর নিয়ম করে 0 হলো standard input, 1 standard output, 2 standard error.
এই সিদ্ধান্তের ফল ছিল বিশাল। একটি প্রোগ্রাম জানেই না তার ইনপুট কীবোর্ড থেকে আসছে, ফাইল থেকে, নাকি আরেকটি প্রোগ্রাম থেকে। তাই ছোট ছোট টুল পাইপ দিয়ে জোড়া যায় — cat log.txt | grep error | sort | uniq -c. Doug McIlroy-র পাইপের ধারণা আর “একটি কাজ ভালোভাবে করো” নীতি মিলে তৈরি হয় Unix দর্শন। পরে Bell Labs-এরই Plan 9 এই ধারণা আরও দূরে নেয় — নেটওয়ার্ক, জানালা, প্রসেস সবই সেখানে ফাইল-ব্যবস্থার অংশ। Linux-এর /proc ফোল্ডারে আজও প্রতিটি চলমান প্রোগ্রামের তথ্য ফাইল হিসেবে পড়া যায়।
Python এই উত্তরাধিকার বহন করে। sys.stdin, sys.stdout, sys.stderr হলো file object — ঠিক open()-এর ফলের মতো, একই read(), write() method-সহ। এজন্যই print(..., file=f) কাজ করে: print শুধু জানে “একটি জিনিস, যার write() আছে”। এই ধারণাকে বলে file-like object: io.StringIO মেমরির একটি string-কে ফাইলের মতো আচরণ করায়, আর নেটওয়ার্ক socket বা HTTP response-কেও ফাইলের মতো পড়া যায়। পর্ব ৩৩-এ এই “আচরণ মিললেই চলবে” নীতির নাম পাবেন duck typing. আপনার একটি ফাংশন যদি পথের নামের বদলে file object নেয়, তাহলে তাকে টেস্টে StringIO দিয়ে, বাস্তবে আসল ফাইল দিয়ে, আর পাইপলাইনে sys.stdin দিয়ে — কোনো বদল ছাড়াই চালানো যায়।
তবে দর্শনটির সীমাও আছে। সবকিছু বাইটের সারি হিসেবে দেখালে কাঠামো (কলাম, টাইপ) হারায় — এজন্যই পরে CSV, JSON, ডেটাবেস লাগে (পর্ব ২৬)। আর Windows-এর জগৎ ঐতিহাসিকভাবে ভিন্ন পথে হেঁটেছে, যদিও সেখানেও “handle” ধারণাটি অনেকটা একই।
মূল উপলব্ধি: একটি সরল, অভিন্ন ইন্টারফেস (open-read-write-close) হাজারো যন্ত্র আর প্রোগ্রামকে একে অপরের সাথে জুড়তে দেয়। ভালো নকশা মানে প্রায়ই “ভিন্ন জিনিসকে একই রকম দেখানো”।
প্র ০২Python 3 কেন text আর binary I/O-কে এত কঠোরভাবে আলাদা করল? PEP 3116 আর io module কী সমস্যার সমাধান করেছিল?
Python 2-এ open() দিত C-র FILE*-এর ওপর একটি পাতলা মোড়ক। read() ফেরত দিত str — যা আসলে বাইটের সারি। লেখা আর বাইটের কোনো পার্থক্য ছিল না, encoding-এর কোনো ধারণাই ফাইলের মধ্যে ছিল না। ইংরেজি ফাইলে সব ঠিক চলত; কিন্তু বাংলা, আরবি বা চীনা লেখা এলেই কোথাও না কোথাও একটি unicode object-এর সাথে বাইট মিশে যেত, আর অনেক দূরের, সম্পর্কহীন একটি লাইনে UnicodeDecodeError ফাটত। ভুলটা কোথায় ঢুকেছে, তা খুঁজে পাওয়া ছিল দুঃস্বপ্ন। তার ওপর text আর binary মোডের পার্থক্য প্ল্যাটফর্মভেদে ভিন্ন ছিল — Linux-এ দুটি একই, Windows-এ newline-এর অনুবাদ — তাই Linux-এ লেখা কোড Windows-এ ছবি ফাইল নষ্ট করত।
২০০৭ সালে Daniel Stutzbach, Guido van Rossum আর Mike Verdone লেখেন PEP 3116 — “New I/O”. প্রস্তাবটি I/O-কে তিন স্তরে সাজায়: সবচেয়ে নিচে raw I/O (FileIO), যা OS-এর সাথে সরাসরি বাইট আদান-প্রদান করে; মাঝে buffered I/O (BufferedReader, BufferedWriter), যা অল্প অল্প read/write-কে বড় টুকরোয় জমিয়ে দক্ষ করে; আর সবার ওপরে text I/O (TextIOWrapper), যা encoding, decoding আর newline অনুবাদ সামলায়। আপনি open("x.txt", encoding="utf-8") লিখলে আসলে পান এই তিন স্তরের একটি স্তূপ; "rb" লিখলে পান শুধু নিচের দুটি। f.buffer দিয়ে text স্তরের নিচের binary স্তরটিও ধরা যায়।
২০০৮ সালের Python 3.0-তে এই নতুন io module ডিফল্ট হয়, আর str/bytes-এর বিভাজনের সাথে মিলে নিয়ম দাঁড়ায়: text মোড শুধু str নেয়-দেয়, binary মোড শুধু bytes. মেশাতে গেলেই সঙ্গে সঙ্গে TypeError — সমস্যা ধরা পড়ে ঠিক যেখানে ঘটে, দূরে নয়। প্রথম সংস্করণটি মূলত Python-এ লেখা ছিল এবং বেশ ধীর ছিল; তাই Python 3.1-এ পুরো io module C-তে নতুন করে লেখা হয়। Python-এ লেখা সংস্করণটি আজও _pyio নামে আছে, পড়ে বোঝার জন্য চমৎকার।
এই কঠোরতার দামও ছিল। Python 2 থেকে 3-এ যাওয়ার সময় হাজারো লাইব্রেরিকে ভেবে দেখতে হয়েছে “এটা কি লেখা, নাকি বাইট?” — যে প্রশ্ন তারা বছরের পর বছর এড়িয়ে চলেছিল। কিন্তু সেই প্রশ্নের উত্তর দেওয়াটাই ছিল আসল সংশোধন।
মূল উপলব্ধি: ভুলকে আগে ও কাছে ধরানো (fail fast) অস্পষ্ট নমনীয়তার চেয়ে ভালো। “এটা লেখা, না বাইট?” — এই প্রশ্ন বাধ্যতামূলক করে Python 3 বাংলা-সহ সব ভাষার ব্যবহারকারীর জন্য ফাইল নির্ভরযোগ্য করেছে।
প্র ০৩pathlib এল কীভাবে? path-কে string থেকে object বানানোর সিদ্ধান্তে কী লাভ, আর কী আপস?
Python-এর প্রথম দুই দশক path মানে ছিল সাধারণ string, আর তার ওপর কাজ চলত ছড়ানো-ছিটানো ফাংশনে: os.path.join, os.path.splitext, os.path.basename, os.listdir, glob.glob, shutil.copy… একটি সাধারণ কাজের জন্যও তিনটি module ঘাঁটতে হতো, আর string বলে যেকোনো string — একটি URL বা একটি বাক্য — ভুল করে path হিসেবে চলে যেত। কয়েক বছর ধরে তৃতীয় পক্ষের লাইব্রেরি (যেমন path.py) object-ভিত্তিক path-এর চেষ্টা করেছিল, কিন্তু কোনোটিই মান হয়নি।
CPython-এর core developer Antoine Pitrou ২০১২ সালে লেখেন PEP 428 — “The pathlib module”, যা তাঁর নিজের PyPI-র pathlib প্যাকেজের ওপর ভিত্তি করে। মূল নকশার দুটি সিদ্ধান্ত গুরুত্বপূর্ণ। এক, “pure” path (PurePath, PurePosixPath, PureWindowsPath) — যা শুধু নামের হিসাব করে, ডিস্ক ছোঁয় না — আর “concrete” path (Path), যা আসল file system-এ কাজ করে, এদের আলাদা রাখা। ফলে Linux সার্ভারে বসেও Windows-এর path নিরাপদে বিশ্লেষণ করা যায়। দুই, / অপারেটরকে জোড়ার কাজে লাগানো — শুরুতে বিতর্কিত, এখন সবচেয়ে প্রিয় অংশ। pathlib Python 3.4-এ (২০১৪) standard library-তে আসে, তবে “provisional” — মানে API বদলাতে পারে — হিসেবে।
প্রথম কয়েক বছর একটি বড় বাধা ছিল: open(), os, shutil-এর অনেক ফাংশন Path নিত না, প্রতিবার str(p) লিখতে হতো। Python 3.6-এ PEP 519 আনে os.PathLike প্রোটোকল আর __fspath__ method — এখন যেকোনো object নিজেকে path হিসেবে পরিচয় দিতে পারে, আর standard library-র প্রায় সব ফাংশন তা মানে। একই সংস্করণে pathlib-এর provisional তকমাও ওঠে। তারপর থেকে ধীরে ধীরে বৈশিষ্ট্য বেড়েছে: is_relative_to() (3.9), Path.walk() (3.12), আর Python 3.14-এ ফাইল কপি ও সরানোর জন্য copy() ও move().
আপসও আছে। object তৈরি আর method ডাকায় সামান্য বাড়তি খরচ — লাখো ফাইলের তালিকায় os.scandir() এখনো দ্রুততর। আর পুরোনো কোডবেস, টিউটোরিয়াল ও AI-এর শেখার ডেটায় os.path-এর আধিপত্য বলে দুই ধাঁচই পড়তে জানা দরকার।
মূল উপলব্ধি: সঠিক টাইপ ভুলকে অসম্ভব করে তোলে। path যখন শুধু string, তখন যেকোনো লেখা path হতে পারে; path যখন object, তখন তার নিজের নিয়ম, নিজের method আর OS অনুযায়ী সঠিক আচরণ থাকে।
প্র ০৪Windows-এ Python-এর ডিফল্ট encoding কেন এত বছর UTF-8 ছিল না? PEP 540 আর PEP 686 কীভাবে এটা বদলাচ্ছে, আর পুরোনো ডিফল্ট রাখার পক্ষে যুক্তি কী ছিল?
Python-এর মূল নীতি ছিল “অপারেটিং সিস্টেমের সাথে মিলে চলো”। একটি text ফাইলের encoding কী, তা ফাইল নিজে বলে না; তাই Python ধরে নিত ব্যবহারকারীর সিস্টেম যে encoding-এ চলে — locale-এর encoding — ফাইলও সেটাতেই। ১৯৯০ আর ২০০০-এর দশকে এটা যুক্তিসংগত ছিল: জাপানের Windows কম্পিউটারের সব ফাইল Shift-JIS-এ, রাশিয়ার কম্পিউটারের cp1251-এ। Notepad-এ লেখা ফাইল Python-এ খুললে মিলবে — এটাই চাওয়া হয়েছিল।
কিন্তু ২০১০-এর দশকে জগৎ বদলে গেল। ওয়েবের প্রায় সব পাতা, প্রায় সব প্রোগ্রামিং টুল, git, JSON — সবাই UTF-8-এ। Linux আর macOS-এর locale-ও UTF-8. ফলে Windows হয়ে দাঁড়াল ব্যতিক্রম: একই Python কোড Linux-এ বাংলা ঠিক পড়ত, Windows-এ UnicodeDecodeError দিত বা নিঃশব্দে mojibake বানাত। Python 3.6-এ PEP 528 ও 529 Windows-এর console আর ফাইলের নাম-এর encoding UTF-8 করে, কিন্তু ফাইলের ভেতরের লেখার ডিফল্ট রয়ে যায় locale.
Victor Stinner-এর PEP 540 (Python 3.7) আনে UTF-8 Mode — -X utf8 বা PYTHONUTF8=1 দিলে locale উপেক্ষা করে সব জায়গায় UTF-8. কিন্তু এটা ঐচ্ছিক, আর নতুনরা জানতই না। Inada Naoki-র PEP 597 (Python 3.10) আনে EncodingWarning, যাতে লাইব্রেরি লেখকরা খুঁজে পান কোথায় encoding বাদ পড়েছে। তারপর Inada-রই PEP 686 সিদ্ধান্ত নেয় UTF-8 Mode-কে ডিফল্ট করার; পরিকল্পনা অনুযায়ী এটি Python 3.15 থেকে কার্যকর হওয়ার কথা।
পুরোনো ডিফল্টের পক্ষে যুক্তিগুলো হালকা ছিল না। বদলালে বিদ্যমান Windows প্রোগ্রাম, যারা স্থানীয় encoding-এর ফাইল নীরবে ঠিকঠাক পড়ত, হঠাৎ error দেবে বা ভুল পড়বে। তাই এত ধাপ — প্রথমে ঐচ্ছিক mode, তারপর সতর্কতা, তারপর বহু বছরের নোটিশ, আর শেষে ডিফল্ট বদল, সাথে বন্ধ করার উপায় (PYTHONUTF8=0). বড় ব্যবহারকারী-ভিত্তির একটি ভাষায় ডিফল্ট বদলানো কত ধীর ও সতর্ক প্রক্রিয়া, এটি তার উদাহরণ।
আপনার জন্য বাস্তব শিক্ষা: ডিফল্টের ওপর ভরসা করবেন না। আপনার কোড Python 3.9 চালানো কোনো অফিসের কম্পিউটারেও চলতে পারে। encoding="utf-8" লেখা ছয়টি শব্দের খরচ, আর না লেখা মানে অন্য কারো কম্পিউটারে বাংলা ডেটা নষ্টের ঝুঁকি।
মূল উপলব্ধি: ডিফল্ট মান হলো ইতিহাসের সিদ্ধান্ত, আর ইতিহাস দ্রুত বদলায় না। স্পষ্ট লেখা (explicit) কোডকে সময় আর প্ল্যাটফর্মের বাইরে নির্ভরযোগ্য রাখে।
প্র ০৫“temp ফাইলে লেখো, তারপর rename” — এই কৌশল crash-এ কেন বাঁচায়? fsync ছাড়া কী ঝুঁকি থাকে, আর এর বাস্তব শিক্ষা কোথায় পাওয়া গেছে?
ফাইলে লেখা একটি মুহূর্তের কাজ নয় — বহু ধাপের যাত্রা। write() প্রথমে Python-এর buffer-এ, flush হলে OS-এর page cache-এ (মেমরিতে), আর OS নিজের সুবিধামতো কিছু সেকেন্ড পরে আসল ডিস্কে লেখে। এই যাত্রার যেকোনো জায়গায় প্রোগ্রাম মরতে পারে, বিদ্যুৎ যেতে পারে। সরাসরি "w"-তে লিখলে মাঝপথে থামা মানে ফাইলে আধা পুরোনো, আধা নতুন বা শূন্য বাইট — যেকোনো অবস্থা।
rename-এর জাদু হলো POSIX মান অনুযায়ী একই file system-এ rename() atomic: যে প্রোগ্রামই ফাইলটি খুলুক, সে হয় পুরো পুরোনো ফাইল দেখবে, নয় পুরো নতুন — কখনো মাঝামাঝি নয়। ফাইলের নাম আর তার বিষয়বস্তুর সংযোগটি (directory entry) এক ধাক্কায় বদলে যায়। ডেটাবেস, git, প্যাকেজ ম্যানেজার, টেক্সট এডিটর — সবাই বহু বছর ধরে এই কৌশল ব্যবহার করে। Python 3.3-এ যোগ হওয়া os.replace() এই কাজটি সব প্ল্যাটফর্মে একভাবে করার চেষ্টা করে (Windows-এ পুরোনো os.rename() লক্ষ্য ফাইল থাকলে error দিত)।
কিন্তু একটি সূক্ষ্ম ফাঁক আছে। rename atomic, কিন্তু সেটি বলে না temp ফাইলের বাইটগুলো ডিস্কে পৌঁছেছে কি না। OS নাম বদলটি আগে ডিস্কে লিখে ফেলতে পারে, আর বিষয়বস্তু তখনো মেমরিতে। এর বিখ্যাত উদাহরণ ২০০৯ সাল: Linux-এর নতুন ext4 file system “delayed allocation” ব্যবহার করত, আর অনেক অ্যাপ্লিকেশন fsync ছাড়াই temp + rename করত। crash-এর পর ব্যবহারকারীরা দেখলেন তাঁদের কনফিগারেশন ফাইলগুলো শূন্য বাইট! বিতর্কের পর ext4-এর রক্ষণাবেক্ষণকারী Ted Ts'o এমন rename-এর আগে ডেটা লেখার একটি সুরক্ষা যোগ করেন, তবে সাথে স্পষ্ট বলেন — অ্যাপ্লিকেশনের উচিত নিজে fsync ডাকা।
তাই পুরো নিরাপদ রেসিপি: temp-এ লিখুন → f.flush() (Python buffer থেকে OS-এ) → os.fsync(f.fileno()) (OS থেকে ডিস্কে) → বন্ধ করুন → os.replace(). POSIX-এ সর্বোচ্চ নিশ্চয়তার জন্য ফোল্ডারটিও খুলে fsync করতে হয়, যাতে নাম বদলটিও ডিস্কে স্থায়ী হয়। আর মনে রাখুন, কিছু ডিস্ক নিজেও ক্যাশ রাখে — নিখুঁত durability হার্ডওয়্যারের সহযোগিতাও চায়। এজন্যই SQLite-এর মতো ডেটাবেস journal ও write-ahead log-এর মতো আরও জটিল কৌশল ব্যবহার করে।
মূল উপলব্ধি: “লেখা হয়েছে” আর “নিরাপদে জমা হয়েছে” এক কথা নয়। atomic rename দেয় সামঞ্জস্য (কেউ আধা ফাইল দেখবে না), আর fsync দেয় স্থায়িত্ব (crash-এর পরও টিকে থাকবে) — দুটি মিলেই নির্ভরযোগ্য লেখা।
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — আগে অনুমান, তারপর চালিয়ে মিলিয়ে নেওয়াই আসল শেখা।
-
মোডের অনুমান: একটি নতুন ফোল্ডারে পরপর চালানো হলো: (ক)
"w"-তে"এক\n"লেখা; (খ)"a"-তে"দুই\n"; (গ)"w"-তে"তিন\n"; (ঘ)"a"-তে"চার\n"; (ঙ)"x"-তে"পাঁচ\n". শেষে ফাইলে কী আছে, আর কোন ধাপে কী error? তারপরsales_diary.pyবদলে মিলিয়ে নিন।(ক) পরে:
এক. (খ) পরে:এক,দুই. (গ)"w"খোলার মুহূর্তেই সব মুছে যায়, তাই শুধুতিন. (ঘ) পরে:তিন,চার. (ঙ) ফাইল আগে থেকেই আছে, তাইFileExistsError— ফাইল বদলায় না। চূড়ান্ত ফাইল: দুটি লাইন,তিনআরচার. শিক্ষা:"w"অতীত মুছে দেয়,"a"অতীত রাখে,"x"অতীত থাকলে কাজই করে না। -
বাংলা শব্দ গোনা (কোড লিখুন): একটি UTF-8 ফাইল
story.txt-এ বাংলা গল্প আছে। এমন একটি প্রোগ্রাম লিখুন যা লাইন ধরে পড়ে প্রতিটি শব্দ কতবার এসেছে গোনে (দাঁড়ি, কমা, প্রশ্নবোধক চিহ্ন বাদ দিয়ে) আর সবচেয়ে বেশি আসা ৫টি শব্দ দেখায়। কোন কোন ফাঁদ আছে, ভাবুন।একটি সমাধান:
from pathlib import Path counts: dict[str, int] = {} with open(Path("story.txt"), encoding="utf-8-sig") as f: for line in f: # বড় ফাইলেও অল্প মেমরি for word in line.split(): word = word.strip("।,;:!?\"'()-—") if word: counts[word] = counts.get(word, 0) + 1 top = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:5] for word, n in top: print(word, n)ফাঁদগুলো: (১) বাংলা দাঁড়ি
।ইংরেজি punctuation-এর তালিকায় নেই, নিজে যোগ করতে হয়। (২)encoding="utf-8-sig"দিলে Notepad-এর BOM প্রথম শব্দের সাথে লেগে থাকে না। (৩) সমান সংখ্যার শব্দের ক্রম নিশ্চিত করতে sort key-তে শব্দটিও দিয়েছি। (৪) একই শব্দ ভিন্ন Unicode রূপে টাইপ হলে (যেমন “য়” দুইভাবে, পর্ব ০৯) আলাদা গোনা হবে — গুরুত্বপূর্ণ কাজে আগেunicodedata.normalize("NFC", line). পর্ব ২৭-এcollections.Counterদিয়ে এটি আরও ছোট হবে। -
Mojibake গোয়েন্দা: আপনার বন্ধুর Windows ল্যাপটপে একটি প্রোগ্রাম
বাংলাশব্দের জায়গায় দেখাচ্ছেবাংলা. কোনো error আসেনি। কী ঘটেছে, কীভাবে নিশ্চিত হবেন, আর কোডে কী বদলাবেন? যদি এই ভুল লেখা আবার ফাইলে সেভ হয়ে গিয়ে থাকে, তখন?প্রতিটি বাংলা অক্ষর তিনটি করে ইউরোপীয় অক্ষর হয়ে গেছে, আর প্রায় সবগুলো
àদিয়ে শুরু — এটি UTF-8 বাইটকে cp1252 হিসেবে decode করার চিহ্ন (UTF-8-এ বাংলার প্রথম বাইটe0, যা cp1252-এà)। কোডেopen()-এ encoding ছিল না, তাই Windows-এর locale encoding (cp1252) ব্যবহার হয়েছে। নিশ্চিত হতেopen(p, "rb").read(12)দিয়ে কাঁচা বাইট দেখুন —e0 a6 ...হলে ফাইল আসলে UTF-8. সমাধান:encoding="utf-8". আর ভুল লেখা আবার সেভ হয়ে গিয়ে থাকলে উল্টো পথে ফেরানো যায়:bad.encode("cp1252").decode("utf-8")— তবে শুধু যদি কোনো বাইটerrors="replace"-এ হারিয়ে না গিয়ে থাকে। এজন্যই mojibake-কে আবার ফাইলে লিখতে দেওয়া বিপজ্জনক। -
AI অডিট: যেকোনো AI-কে বলুন: “Python-এ একটি স্ক্রিপ্ট লেখো যা আমার Downloads ফোল্ডারের ফাইলগুলো extension অনুযায়ী আলাদা ফোল্ডারে সাজায়।” উত্তরটি এই পর্বের চেকলিস্ট দিয়ে যাচাই করুন। আসল ফোল্ডারে চালানোর আগে কী কী পরীক্ষা করবেন?
সাধারণত যা দেখবেন:
os.listdir()আরos.path.join(কাজ করে, তবে পুরোনো ধাঁচ); ফোল্ডারের তালিকা sorted নয়;.JPGআর.jpgদুটি আলাদা ফোল্ডারে যায় (lower()নেই); extension নেই এমন ফাইলের জন্য খালি নামের ফোল্ডার; আর সবচেয়ে বিপজ্জনক — গন্তব্যে একই নামের ফাইল থাকলেrename/shutil.movePOSIX-এ পুরোনোটিকে নিঃশব্দে মুছে দিতে পারে। নিরাপদ পরীক্ষা: (১) প্রথমে একটি নকল ফোল্ডারে কয়েকটি খালি ফাইল বানিয়ে চালান (organize.py-এর মতো); (২) AI-কে একটি “dry run” বিকল্প যোগ করতে বলুন, যা শুধু কী সরানো হবে তা প্রিন্ট করে; (৩) নামের সংঘর্ষে নতুন নাম (যেমনcv (1).pdf) দিতে বলুন; (৪) তারপরই আসল ফোল্ডারে, আর অবশ্যই ব্যাকআপ রেখে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি জরুরি ফাইল (যেমন দামের তালিকা) crash-এও নিরাপদে আপডেট করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📁 আসল ফাইলের একই ফোল্ডারে একটি temp ফাইল খোলা
- ✍️ নতুন পুরো লেখা temp ফাইলে লেখা
- 💾 flush() আর os.fsync() দিয়ে বাইট ডিস্কে পাঠানো
- 🔒 temp ফাইল বন্ধ করা (with ব্লক শেষ)
- 🔁 os.replace() দিয়ে temp-কে আসল নামে বসানো
১০০ লাইনের একটি ফাইল open("data.txt", "w", encoding="utf-8") দিয়ে খুলে কিছু না লিখেই বন্ধ করলে ফাইলে কী থাকে?
- আগের ১০০ লাইন অক্ষত থাকে
- ফাইলটি খালি — ০ বাইট
- FileExistsError আসে, ফাইল অক্ষত
- ফাইলটি ডিস্ক থেকে মুছে যায়
Python-এ কোন মোড নতুন ফাইল বানায়, কিন্তু একই নামের ফাইল আগে থেকে থাকলে কিছু না বদলে error দেয়?
- "w"
- "a"
- "r+"
- "x"
কয়েক GB-র একটি text লগ ফাইল প্রক্রিয়া করতে কম মেমরিতে সবচেয়ে উপযুক্ত উপায় কোনটি?
with open(...) as f:তারপরfor line in f:text = f.read()তারপরtext.split("\n")for line in f.readlines():Path(p).read_text(encoding="utf-8").splitlines()
Path("shop/2026/report.final.pdf").stem-এর মান কী?
- 'report.final.pdf'
- 'report'
- 'report.final'
- '.pdf'
Excel-এ “CSV UTF-8” হিসেবে সেভ করা একটি ফাইল encoding="utf-8" দিয়ে পড়লে প্রথম লাইনের শুরুতে একটি অদৃশ্য '\ufeff' পাওয়া যায়। সবচেয়ে সহজ সমাধান কী?
encoding="cp1252"দিয়ে পড়াencoding="utf-8-sig"দিয়ে পড়া"rb"মোডে খোলাerrors="replace"যোগ করা
জরুরি ফাইল আপডেটের সময় সরাসরি লেখার বদলে temp ফাইলে লিখে os.replace() করার মূল কারণ কী?
- এতে লেখা অনেক দ্রুত হয়
- এতে encoding নিজে থেকে UTF-8 হয়
- এতে ফাইলের আকার ছোট হয়
- লেখার মাঝে crash হলেও আসল ফাইল কখনো আধা-লেখা অবস্থায় থাকে না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ফাইল = বাইট + encoding-এর চুক্তি। text মোড দেয়
str, binary মোড ("b") দেয়bytes; ছবি, PDF, zip সবসময় binary. "w"খোলার মুহূর্তেই মুছে ফেলে,"a"শেষে যোগ করে,"x"ফাইল থাকলে error দেয়। ইতিহাস জমাতে"a".- ফাইল মানেই
with open(...) as f:— error হলেও বন্ধ ও flush নিশ্চিত। বড় ফাইলfor line in f:বা chunk ধরে। - সবসময়
encoding="utf-8"; Excel/Notepad-এর BOM থাকলে"utf-8-sig". ভুল encoding নিঃশব্দে mojibake বানায়। - path-এর জন্য
pathlibআর/; ফোল্ডারের তালিকাsorted; নির্দিষ্ট error ধরুন; জরুরি ফাইল temp +replace(); বাইরের pathresolve()করে যাচাই।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৬ · CSV আর JSON — ডেটা আদান-প্রদানপরবর্তী পর্বস্প্রেডশিট আর API-র ডেটা নির্ভুলভাবে পড়া-লেখা।
- Python প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — ৮টি মডিউল, কোন পর্ব কখন।
- C++ প্রোগ্রামিং · ফাইল I/O ও filesystemতুলনা করুনfstream, RAII আর std::filesystem — নিচের স্তরে একই ধারণা।
- C প্রোগ্রামিং · ফাইল I/Oগভীরে যান
FILE*,fopen, buffer আরfclose— Python-এর file object-এর ভিত।