এই পর্বে যা শিখবেন
- ডেটা আদান-প্রদানের ফরম্যাট কেন লাগে, আর CSV, JSON ও database — কোনটি কোন কাজের
- হাতে
split(",")কেন ভুল, আরcsv.reader,writer,DictReader,DictWriterদিয়ে ঠিকভাবে পড়া-লেখা newline="", delimiter,utf-8-sigআর str থেকে সংখ্যায় রূপান্তর — বাস্তব CSV-র তিনটি ফাঁদ- JSON-এর ধরন-মানচিত্র,
ensure_ascii,indent,sort_keys,default=hook আর ভাঙা JSON সামলানো - JSON Lines, config ফাইল, API-র JSON — আর কেন
pickleওevalঅবিশ্বস্ত ডেটায় বিপজ্জনক
১ · প্রোগ্রামেরা কীভাবে ডেটা ভাগ করে — একমত হওয়া লেখার ফরম্যাট
পর্ব ২১-এ পরীক্ষার ফলের একটি টুল বানিয়েছিলেন। সেখানে সব ছাত্রের নাম আর নম্বর থাকত একটি dict-এ — যতক্ষণ প্রোগ্রাম চলছে, ততক্ষণ। প্রোগ্রাম বন্ধ হলেই সব হারিয়ে যেত। পর্ব ২৫-এ শিখেছেন কীভাবে ফাইলে লেখা যায়। কিন্তু একটি প্রশ্ন বাকি: ফাইলে কোন আকারে লিখবেন? একটি nested dict তো সরাসরি ডিস্কে বসানো যায় না — ফাইলে থাকে শুধু লেখা (বা byte).
আরও বড় প্রশ্ন: সেই ফাইল যদি অন্য কেউ পড়ে? স্কুলের শিক্ষক Google Sheets-এ নম্বর তুলেছেন, আপনার Python প্রোগ্রাম সেটি পড়বে। আপনার প্রোগ্রামের ফল একটি ওয়েবসাইট দেখাবে, যা JavaScript-এ লেখা। একটি মোবাইল অ্যাপ সার্ভার থেকে আবহাওয়ার খবর চাইবে। প্রতিটি ক্ষেত্রে দুটি আলাদা প্রোগ্রাম — হয়তো আলাদা ভাষায়, আলাদা কম্পিউটারে — একই ডেটা বুঝতে হবে। এর একমাত্র উপায়: আগে থেকে একমত হওয়া একটি ডেটা ফরম্যাট — লেখার এমন নিয়ম, যা দুই পক্ষই জানে।
বাংলাদেশের যেকোনো জেলা থেকে চিঠি পাঠাতে খামের ওপর একটি নির্দিষ্ট নিয়মে ঠিকানা লেখেন — নাম, বাড়ি, থানা, জেলা, পোস্টকোড। প্রেরক আর ডাকঘরের কর্মী কখনো দেখা করেননি, তবু কর্মী ঠিকানাটি পড়তে পারেন, কারণ দুজনই একই নিয়ম জানেন। ডেটা ফরম্যাট হলো প্রোগ্রামদের সেই “ঠিকানা লেখার নিয়ম”. নিয়ম ভাঙলে — পোস্টকোডের ঘরে থানার নাম — চিঠি হারায়; প্রোগ্রামে ডেটা ভুল পড়া হয়।
মেমরির ভেতরের object (dict, list) থেকে এমন লেখায় বদলানোকে বলে serialization, আর লেখা থেকে আবার object বানানোকে deserialization. এই পর্বে দুটি সবচেয়ে প্রচলিত লেখা-ভিত্তিক ফরম্যাট শিখবেন — দুটিরই জন্য Python-এর standard library-তে তৈরি module আছে, কিছু install করতে হবে না।
📊 CSV — টেবিল
Comma-Separated Values. প্রতিটি লাইন একটি সারি, কমা দিয়ে আলাদা করা ঘরগুলো কলাম। প্রথম লাইনে সাধারণত কলামের নাম। Excel আর Google Sheets সরাসরি খোলে ও বানায়। সমতল (flat) টেবিলের জন্য আদর্শ — মার্কশিট, বিক্রির হিসাব, জেলার তালিকা।
import csv🌳 JSON — গাছের মতো ডেটা
JavaScript Object Notation. object (চাবি-মানের জোড়া, Python-এর dict-এর মতো) আর array (list-এর মতো), যেগুলো একটির ভেতরে আরেকটি বসানো যায়। ওয়েব API, config ফাইল আর অ্যাপের সেটিংসের সাধারণ ভাষা।
import jsonএকই ছাত্রের তথ্য দুই ফরম্যাটে দেখুন। CSV-তে প্রতিটি ছাত্র একটি সারি, আর প্রতিটি ঘরে একটি মাত্র মান:
roll,name,address,bangla,math
101,রিয়া,"মিরপুর, ঢাকা",78,91
102,জয়,"আম্বরখানা, সিলেট",65,72JSON-এ একই ডেটা, কিন্তু এবার একটি ঘরে একটি list রাখা যায়, আর ঠিকানাকে ভেঙে ভেতরে আরেকটি object বানানো যায়:
{
"roll": 101,
"name": "রিয়া",
"address": {"area": "মিরপুর", "district": "ঢাকা"},
"marks": [78, 91],
"passed": true
}CSV প্রশ্ন করে “কোন সারি, কোন কলাম?” — JSON প্রশ্ন করে “কোন চাবির ভেতরে কোন চাবি?” ডেটা যদি স্বাভাবিকভাবে একটি টেবিল হয়, যেখানে প্রতিটি সারির একই কলাম, তবে CSV. ডেটার ভেতরে যদি list, ভেতরের রেকর্ড বা ঐচ্ছিক ঘর থাকে, তবে JSON. আর Python-এর csv ও json module এই দুই ফরম্যাট আর আপনার list/dict-এর মধ্যে দোভাষীর কাজ করে।
২ · CSV দেখতে সরল — তবু split(",") কেন ভাঙে
CSV দেখে প্রথম চিন্তা আসে: “এ তো সহজ! প্রতিটি লাইন পড়ব, line.split(",") দিয়ে ভাগ করব”. পর্ব ০৯-এর string method দিয়েই হয়ে যাবে। অনেক AI-ও ঠিক এই কোড দেয়। সমস্যা হলো, ঘরের ভেতরে কমা থাকতে পারে — ঠিকানায় (“মিরপুর, ঢাকা”), পণ্যের নামে (“চাল, মিনিকেট”), টাকার অঙ্কে (“1,250”). CSV এর সমাধান করে উদ্ধৃতি চিহ্ন দিয়ে: যে ঘরে কমা আছে, তাকে "…"-এর ভেতরে রাখা হয়। উদ্ধৃতির ভেতরের কমা ঘর আলাদা করে না, ডেটার অংশ। split এই নিয়ম জানে না। ধাপে ধাপে দেখুন কী ঘটে:
- লাইন
৫টি ঘরএকটি ঘরে কমা - split(",")
৬টি টুকরোপ্রতিটি কমায় কাটে - ভুল কলাম
চুপচাপকোনো error নেই - csv.reader
উদ্ধৃতি বোঝেঅক্ষর ধরে পড়ে - ৫টি ঘর
ঠিক ঠিকলাইন-ভাঙাও সামলায়
101,রিয়া,"মিরপুর, ঢাকা",78,91 # ৫টি কলাম চাই: roll, name, address, bangla, math # ঠিকানার ভেতরে একটি কমা — তাই উদ্ধৃতি দিয়ে ঘেরা
['101', 'রিয়া', '"মিরপুর', ' ঢাকা"', '78', '91'] # len = 6 — একটি বেশি! # উদ্ধৃতি চিহ্নগুলো ডেটার ভেতরে ঢুকে গেছে
address = '"মিরপুর' bangla = ' ঢাকা"' → int() দিলে ValueError math = '78' → আসলে বাংলার নম্বর! # zip(header, parts) করলে শেষ টুকরো '91' চুপচাপ বাদ পড়ে
সাধারণ অবস্থা: , → ঘর শেষ
" → উদ্ধৃতির ভেতরে ঢুকলাম
উদ্ধৃতির ভেতরে: , → সাধারণ অক্ষর, ডেটার অংশ
"" → একটি আসল " চিহ্ন
" → উদ্ধৃতি শেষ
['101', 'রিয়া', 'মিরপুর, ঢাকা', '78', '91'] # ঘরের ভেতরে লাইন-ভাঙা থাকলেও: a,"line1 line2",b → [['a', 'line1\nline2', 'b']]
- একটি সাধারণ সারি। শিক্ষকের Google Sheets থেকে নামানো মার্কশিটের একটি লাইন। পাঁচটি কলাম, কিন্তু ঠিকানার ঘরে একটি কমা আছে। Sheets নিজেই সেই ঘরকে উদ্ধৃতি দিয়ে ঘিরে দিয়েছে — এটাই CSV-র নিয়ম।
split(",")প্রতিটি কমায় কাটে। উদ্ধৃতির ভেতর-বাহির সে বোঝে না। ফল ৬টি টুকরো, আর উদ্ধৃতি চিহ্ন দুটো ডেটার মধ্যে আটকে গেছে:'"মিরপুর'আর' ঢাকা"'.- সবচেয়ে বিপজ্জনক অংশ — চুপচাপ ভুল। হেডারের সাথে মেলালে “math” কলামে এসে বসে বাংলার নম্বর 78. যে সারিতে ঠিকানায় কমা নেই, সেগুলো ঠিকই থাকে — তাই পরীক্ষার ছোট ডেটায় বাগ ধরা পড়ে না। হাজার ছাত্রের ফাইলে কয়েকজনের ফল ভুল হয়, কেউ টের পায় না।
csv.readerঅক্ষর ধরে পড়ে। সে মনে রাখে এই মুহূর্তে উদ্ধৃতির ভেতরে আছে কি না। ভেতরে থাকলে কমা সাধারণ অক্ষর। ঘরের ভেতরে আসল উদ্ধৃতি চিহ্ন লিখতে হলে CSV-তে দুটো পাশাপাশি লেখা হয় ("") — reader সেটিও বোঝে।- ঠিক ৫টি ঘর। উদ্ধৃতি চিহ্ন সরিয়ে আসল ডেটা
'মিরপুর, ঢাকা'. আর একটি ঘরের ভেতরে লাইন-ভাঙা থাকলে (যেমন “মন্তব্য” কলামে দুই লাইন), লাইন ধরে পড়া পুরো সারিটিকেই দুই ভাগ করে ফেলত; csv.reader পরের লাইন পর্যন্ত পড়ে ঘরটি জোড়া দেয়। এই কারণেই নিয়ম: CSV কখনো হাতে ভাগ করবেন না।
নিয়মটা জানে
csv module, split জানে না৩ · csv.reader আর csv.writer — আর তিনটি বাস্তব ফাঁদ
পর্ব ২৫-এ with open(...) দিয়ে ফাইল খোলা শিখেছেন। CSV পড়া তার ওপর একটি ছোট স্তর মাত্র। csv.reader(f) একটি iterator দেয়: প্রতিবার লুপ ঘুরলে একটি সারি, একটি list of str হিসেবে। লেখার জন্য csv.writer(f), যার writerow(list) একটি সারি আর writerows(...) অনেকগুলো সারি লেখে — দরকার হলে উদ্ধৃতি নিজেই বসায়।
import csv
with open("marks.csv", newline="", encoding="utf-8") as f:
for row in csv.reader(f):
print(row) # ['101', 'রিয়া', 'মিরপুর, ঢাকা', '78', '91']
rows = [["district", "division"], ["নাটোর", "রাজশাহী"], ["ভোলা", "বরিশাল"]]
with open("districts.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)দুই জায়গাতেই খেয়াল করুন একটি অদ্ভুত argument: newline="". এটিই প্রথম ফাঁদ।
ফাঁদ ১: newline="" না দিলে
পর্ব ২৫-এ দেখেছেন, text mode-এ Python লাইন-শেষের চিহ্ন নিজে অনুবাদ করে (newline translation): পড়ার সময় \r\n বা \r-কে \n বানায়, আর Windows-এ লেখার সময় প্রতিটি \n-কে \r\n. সাধারণ লেখার জন্য এটা সুবিধা। কিন্তু csv module লাইন-শেষ নিজেই সামলাতে চায়: CSV-র প্রচলিত নিয়মে (RFC 4180) প্রতিটি সারি শেষ হয় \r\n দিয়ে, তাই csv.writer ডিফল্টভাবে নিজেই \r\n লেখে। এখন Windows-এ newline="" না দিলে Python সেই \n-কে আবার \r\n বানায় — ফাইলে যায় \r\r\n. Excel-এ খুললে প্রতিটি সারির পরে একটি ফাঁকা সারি. পড়ার সময়ও সমস্যা: উদ্ধৃতির ভেতরের লাইন-ভাঙা অনুবাদ হয়ে বদলে যেতে পারে। Python-এর ডকুমেন্টেশন তাই স্পষ্ট বলে — csv-এর জন্য ফাইল সবসময় newline="" দিয়ে খুলুন। Linux বা Mac-এ এই বাগ চোখে পড়ে না, তাই কোড “আমার মেশিনে তো ঠিক আছে” হয়ে Windows ব্যবহারকারীর কাছে গিয়ে ভাঙে।
ফাঁদ ২: সব দেশে কমা নয় — delimiter
অনেক ইউরোপীয় দেশে দশমিক বিন্দুর জায়গায় কমা লেখা হয় (3,5). সেসব দেশের Excel তাই CSV-তে ঘর আলাদা করতে কমার বদলে সেমিকোলন ব্যবহার করে — নাম CSV হলেও! কোনো ব্যাংক বা বিদেশি প্রতিষ্ঠানের ফাইলে এমন পেতে পারেন, আবার কখনো ট্যাব দিয়ে আলাদা (TSV). ঘর আলাদা করার চিহ্নটিকে বলে delimiter, আর reader-কে তা বলে দিতে হয়: csv.reader(f, delimiter=";") বা ট্যাবের জন্য delimiter="\t". ফাইলের প্রথম কয়েক লাইন দেখে delimiter অনুমান করার জন্য csv.Sniffer আছে, তবে অনুমান তো অনুমানই — জানা থাকলে স্পষ্ট করে বলে দেওয়া ভালো।
ফাঁদ ৩: Excel আর বাংলা — utf-8-sig
পর্ব ২৫-এ শিখেছেন, বাংলা লেখা ফাইলে রাখতে encoding ঠিক করতে হয়, আর আধুনিক মান UTF-8. Excel এখানে একটু জেদি। Excel-এ “CSV UTF-8” হিসেবে সেভ করলে সে ফাইলের একদম শুরুতে তিনটি অদৃশ্য byte বসায় — BOM (byte order mark), যাতে পরে খোলার সময় বুঝতে পারে ফাইলটি UTF-8. সাধারণ encoding="utf-8" দিয়ে পড়লে সেই চিহ্ন প্রথম কলামের নামের সাথে লেগে থাকে: হেডার হয়ে যায় 'roll', আর row["roll"] দেয় KeyError — অথচ চোখে দেখতে সব ঠিক।
উল্টো দিকও আছে। আপনার প্রোগ্রাম BOM ছাড়া UTF-8 CSV লিখলে, Windows-এ সেটি double-click করে Excel-এ খুললে বাংলা অনেক সময় অর্থহীন চিহ্নে (যেমন রি) ভরে যায়, কারণ Excel তখন পুরোনো কোনো encoding ধরে নেয়। দুই সমস্যারই সমাধান একটি encoding নাম:
| পরিস্থিতি | encoding | কেন |
|---|---|---|
| Excel-এর বানানো CSV পড়া | "utf-8-sig" | শুরুর BOM থাকলে সরিয়ে দেয়, না থাকলে সাধারণ UTF-8-এর মতো পড়ে |
| এমন CSV লেখা, যা কেউ Excel-এ খুলবে | "utf-8-sig" | BOM বসায়, তাই Excel বাংলা ঠিক দেখায় |
| অন্য প্রোগ্রাম বা সার্ভারের জন্য CSV | "utf-8" | অনেক টুল BOM আশা করে না |
| Google Sheets থেকে নামানো CSV | "utf-8" (বা নিরাপদে "utf-8-sig") | সাধারণত BOM ছাড়া UTF-8; utf-8-sig দুই ক্ষেত্রেই কাজ করে |
পুরোনো Excel-এর “CSV (Comma delimited)” সাবধান। এই পুরোনো ধরনে সেভ করলে Excel UTF-8 নয়, কম্পিউটারের পুরোনো আঞ্চলিক encoding ব্যবহার করে, আর বাংলা অক্ষর প্রায়ই ? হয়ে চিরতরে হারিয়ে যায় — কোনো encoding দিয়ে পড়েই আর ফেরানো যায় না। কারও কাছ থেকে বাংলা CSV চাইলে বলে দিন “CSV UTF-8” হিসেবে সেভ করতে, বা Google Sheets থেকে নামাতে।
৪ · DictReader আর DictWriter — হেডার দিয়ে সারিকে dict বানানো
csv.reader-এর প্রতিটি সারি একটি list, তাই কলাম পেতে লিখতে হয় row[3]. কিন্তু “3 মানে কোন কলাম?” — ছয় মাস পরে কেউ মনে রাখবে না। আর শিক্ষক যদি Sheets-এ একটি নতুন কলাম মাঝখানে যোগ করেন, সব index এক ঘর সরে যাবে। সমাধান: প্রথম লাইনের header row থেকে কলামের নাম নিয়ে প্রতিটি সারিকে একটি dict বানানো। ঠিক এই কাজ করে csv.DictReader:
1with open("marks.csv", newline="", encoding="utf-8-sig") as f:2 for row in csv.DictReader(f):3 total = int(row["bangla"]) + int(row["math"])4 print(row["name"], total)
| fieldnames | |
| row | |
| total |
- ফাইল খোলা — তিনটি ঠিক argument.
newline=""(csv নিজে লাইন-শেষ সামলাবে), আরutf-8-sig(ফাইলটি Excel থেকে এলে শুরুর BOM সরে যাবে, না হলেও ক্ষতি নেই)। এখনো কিছু পড়া হয়নি। - প্রথম লাইন হেডার হয়ে যায়। লুপ শুরু হতেই DictReader প্রথম সারিটি পড়ে কলামের নাম হিসেবে
fieldnames-এ রাখে। এটি ডেটা হিসেবে লুপে আসে না। ফাইলে হেডার না থাকলে নিজে দিতে পারেন:csv.DictReader(f, fieldnames=[...]). - প্রতিটি সারি একটি dict. চাবি কলামের নাম, মান সেই ঘরের লেখা। এখন
row["bangla"]লেখা যায় — কলামের ক্রম বদলালেও কোড ঠিক থাকে। কিন্তু লক্ষ করুন:'78', উদ্ধৃতিসহ। CSV-তে সব ঘর str — CSV ফাইল নিজে জানে না কোনটি সংখ্যা। - হিসাবের আগে রূপান্তর।
int(row["bangla"])দিয়ে str থেকে int. রূপান্তর ভুলে গেলেrow["bangla"] + row["math"]দিত'7891'— দুটি string জোড়া লাগানো, যোগ নয়। এমন বাগ কোনো error দেয় না। - ফাঁকা ঘর —
''. জয়ের গণিতের নম্বর শিক্ষক এখনো তোলেননি, ঘরটি ফাঁকা। DictReader ফাঁকা ঘরকে ফাঁকা string দেয়, আরint('')ValueErrorছোড়ে। বাস্তব ফাইলে এমন হবেই — তাই পর্ব ২৪-এর try/except দিয়ে বা আগেif row["math"]যাচাই করে সামলান, আর ঠিক করুন ফাঁকা মানে “অনুপস্থিত” নাকি 0.
দুটি খুঁটিনাটি জেনে রাখুন। কোনো সারিতে হেডারের চেয়ে কম ঘর থাকলে বাকি চাবিগুলোর মান হয় None (restval দিয়ে বদলানো যায়); বেশি থাকলে বাড়তিগুলো একটি list হয়ে None চাবির নিচে জমা হয়। দুটিই সাধারণত ভাঙা ফাইলের লক্ষণ — চোখ রাখুন।
লেখা — DictWriter
উল্টো কাজের জন্য csv.DictWriter. তাকে আগে বলে দিতে হয় কোন কোন কলাম, কোন ক্রমে (fieldnames); তারপর writeheader() হেডার লাইন লেখে, আর writerow(dict) প্রতিটি dict থেকে ঠিক সেই ক্রমে ঘর বসায়। dict-এ কোনো চাবি না থাকলে ঘরটি ফাঁকা থাকে; আর dict-এ fieldnames-এর বাইরের কোনো চাবি থাকলে ডিফল্টভাবে ValueError আসে — বানান ভুল ধরার জন্য ভালো। এবার পুরো কাজটি একসাথে: শিক্ষকের মার্কশিট পড়ে প্রত্যেকের গড় বের করে একটি নতুন results.csv লেখা। কোডটি নিজের কাজের ফোল্ডারেই নমুনা ফাইল বানিয়ে নেয়:
import csv
from pathlib import Path
Path("marks.csv").write_text(
"roll,name,address,bangla,math\n"
'101,রিয়া,"মিরপুর, ঢাকা",78,91\n'
'102,জয়,"আম্বরখানা, সিলেট",65,72\n', encoding="utf-8")
line = '101,রিয়া,"মিরপুর, ঢাকা",78,91'
print(len(line.split(",")), next(csv.reader([line])))
with open("marks.csv", newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
print(rows[0]["address"], type(rows[0]["math"]).__name__)
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["roll", "name", "average"])
writer.writeheader()
for r in rows:
avg = (int(r["bangla"]) + int(r["math"])) / 2
writer.writerow({"roll": r["roll"], "name": r["name"], "average": avg})
print(Path("results.csv").read_text(encoding="utf-8"), end="")প্রথম লাইন 6 ['101', 'রিয়া', 'মিরপুর, ঢাকা', '78', '91'] — একই লাইন split করলে ৬টি টুকরো, csv.reader দিলে ঠিক ৫টি ঘর। csv.reader যেকোনো লাইনের সংগ্রহ নেয়, তাই এখানে ফাইল ছাড়াই একটি list দেওয়া গেছে। দ্বিতীয় লাইন মিরপুর, ঢাকা str — DictReader ঠিকানাটি অক্ষত পেয়েছে, আর নম্বরের ঘরটিও str. তারপর results.csv-এর পুরো লেখা: হেডার roll,name,average, তারপর 101,রিয়া,84.5 আর 102,জয়,68.5. DictWriter float 84.5-কে নিজেই লেখায় বদলেছে। ফাইলে প্রতিটি সারির শেষে আসলে \r\n লেখা হয়েছে; read_text text mode-এ পড়ে বলে Python তা \n করে দেখিয়েছে।
আরও একটি ধাপ — সারাংশ রিপোর্ট
বাস্তবে শিক্ষক শুধু গড় চান না; চান একটি ছোট সারাংশ — কতজন পাস, সর্বোচ্চ কে, প্রতিটি বিষয়ের গড়। কৌশল একই: পড়ার সময় str থেকে সংখ্যায় বদলান, পর্ব ১৬-১৮-এর dict আর comprehension দিয়ে হিসাব করুন, তারপর csv.writer দিয়ে ["বিষয়", "গড়"] ধাঁচের সারি লিখুন। একটি অভ্যাস করে নিন: সীমানায় রূপান্তর. ফাইল থেকে পড়ামাত্র ডেটাকে সঠিক ধরনে আনুন (int, float, তারিখ), প্রোগ্রামের ভেতরে শুধু পরিষ্কার ডেটা নিয়ে কাজ করুন, আর লেখার ঠিক আগে আবার লেখায় বদলান। তাহলে প্রোগ্রামের মাঝখানে কোথাও "78" + 1 ধরনের ভুল লুকিয়ে থাকে না।
csv-এর আরেকটি বিকল্প আছে: csv.reader(f, quoting=csv.QUOTE_NONNUMERIC) উদ্ধৃতি ছাড়া সব ঘরকে float বানিয়ে দেয়। কিন্তু তখন রোল 101-ও হয়ে যায় 101.0, আর উদ্ধৃতিহীন কোনো লেখা থাকলে ValueError. বেশিরভাগ ক্ষেত্রে নিজে কলাম ধরে রূপান্তর করাই পরিষ্কার।
৫ · JSON — object আর array, আর Python-এর সাথে ধরনের মানচিত্র
CSV-তে একটি ঘরে একটি মান। কিন্তু পর্ব ২১-এর ফল-টুলের ডেটা দেখুন: প্রতিটি ছাত্রের একটি নাম আর একটি list নম্বর, আর সবাই মিলে একটি dict of dicts. একে CSV-তে রাখতে হলে নম্বরগুলো আলাদা কলামে ভাঙতে হবে, আর বিষয় বাড়লে কলামও বাড়বে। JSON এই ধরনের ডেটার জন্যই বানানো। এর মাত্র ছয় ধরনের মান: object {}, array [], string, number, true/false আর null. আর object ও array একে অপরের ভেতরে যত গভীরে খুশি বসানো যায় — পর্ব ১৬-এর nested dict-এর মতোই।
Python-এর json module এই ছয় ধরনকে Python-এর ধরনে অনুবাদ করে। মানচিত্রটি মুখস্থ রাখার মতো:
| Python (লেখার সময়) | JSON | Python (পড়ার সময়) |
|---|---|---|
dict | object {"k": v} | dict |
list, tuple | array [1, 2] | সবসময় list — tuple হারায় |
str | string "…" (শুধু দ্বিগুণ উদ্ধৃতি) | str |
int, float | number 78, 84.5 | int বা float |
True / False | true / false | True / False |
None | null | None |
dict-এর চাবি 101, True | সবসময় string "101", "true" | str চাবি — int হারায় |
set, datetime, Decimal, নিজের class | — | সরাসরি যায় না: TypeError |
চারটি function — s থাকলে string
json module-এর মূল কাজ চারটি function-এ। নাম মনে রাখার কৌশল: শেষে s মানে string.
| function | কী থেকে | কী তে |
|---|---|---|
json.dumps(obj) | Python object | JSON লেখা — একটি str |
json.loads(text) | JSON লেখা (str বা bytes) | Python object |
json.dump(obj, f) | Python object | সরাসরি খোলা ফাইলে লেখা |
json.load(f) | খোলা ফাইল | Python object |
একটি dict-কে JSON বানিয়ে আবার ফিরিয়ে আনলে কী কী বদলায়, ধাপে ধাপে দেখুন — এই round trip না বুঝলে পরে অদ্ভুত KeyError পাবেন:
- Python dict
dbমেমরিতে object - json.dumps
serializeobject → লেখা - JSON লেখা
strফাইলে বা নেটে যায় - json.loads
deserializeলেখা → object - নতুন dict
back == db?False!
db = {
"rahim": {"name": "Rahim", "marks": (78, 85, 90)},
101: "রোল",
}
dict → object { }
"rahim", "name" → string
(78, 85, 90) → array [78, 85, 90] tuple বলে কিছু JSON-এ নেই
101 (চাবি) → "101" JSON চাবি শুধু string
"রোল" → "\u09b0\u09cb\u09b2" ডিফল্টে বাংলা escape হয়
{"rahim": {"name": "Rahim", "marks": [78, 85, 90]}, "101": "\u09b0\u09cb\u09b2"}
# এটি এখন শুধু অক্ষরের সারি — ফাইলে লেখা যায়,
# JavaScript, Java, Go যেকোনো ভাষা পড়তে পারে
object → dict array [78, 85, 90] → list আগে tuple ছিল — সেই তথ্য JSON-এ ছিলই না "101" → str চাবি "101" "\u09b0\u09cb\u09b2" → "রোল" escape খুলে আবার বাংলা
back == db → False back["rahim"]["marks"] → [78, 85, 90] list(back) → ['rahim', '101'] back[101] → KeyError: 101 back["101"] লিখতে হবে
- শুরু — একটি সাধারণ Python dict. পর্ব ২১-এর ফল-টুলের মতো ডেটা, সাথে দুটি ইচ্ছাকৃত “বিপজ্জনক” জিনিস: নম্বরগুলো tuple-এ, আর একটি চাবি int (101). Python-এ দুটিই সম্পূর্ণ বৈধ।
json.dumps— অনুবাদ। প্রতিটি মান উপরের মানচিত্র ধরে বদলায়। JSON-এ tuple নেই, তাই array. JSON object-এর চাবি শুধু string হতে পারে, তাই int 101 হয়ে যায়"101". আর ডিফল্টে প্রতিটি অ-ASCII অক্ষর\uXXXXরূপে লেখা হয় — অংশ ৬-এ এর সমাধান।- এখন শুধু লেখা।
textএকটি str — আর কোনো dict নয়। এটিই ফাইলে যাবে, বা ইন্টারনেটে অন্য প্রোগ্রামের কাছে। JSON ভাষা-নিরপেক্ষ: যে পড়বে সে Python-এর tuple বা int চাবির কথা জানেই না। json.loads— উল্টো অনুবাদ। লেখায় যা আছে, Python শুধু সেটুকুই জানে। array থেকে কোনটি আগে tuple ছিল, বা কোন string চাবি আগে int ছিল — সেই তথ্য JSON-এ ছিলই না, তাই ফিরে আসা সম্ভব নয়। প্রতিটি array হয় list, প্রতিটি চাবি str.- ফল — প্রায় একই, কিন্তু সমান নয়।
back == dbFalse, কারণ চাবি 101 আর"101"আলাদা, আর tuple ও list কখনো সমান নয়। সবচেয়ে প্রচলিত বাগ: JSON থেকে পড়ার পরback[101]লিখেKeyError. সমাধান: JSON-এ রাখার ডেটায় শুরু থেকেই str চাবি ব্যবহার করুন, বা পড়ার পর নিজেint()দিয়ে বদলে নিন।
print(d)-এর আউটপুট JSON নয়। Python dict ছাপলে আসে {'pass': True, 'remark': None} — একক উদ্ধৃতি, বড় হাতের True, None. JSON-এ হতে হবে {"pass": true, "remark": null}. তাই অন্য প্রোগ্রামের জন্য JSON বানাতে কখনো str(d) বা f-string দিয়ে হাতে জোড়া লাগাবেন না — সবসময় json.dumps. হাতে বানালে বাংলা নামে একটি " চিহ্ন থাকলেই পুরো JSON ভেঙে যায়।
৬ · বাংলা পড়ার মতো রাখা, সুন্দর করে সাজানো, আর যা JSON-এ যায় না
উপরের stepper-এ দেখেছেন, "রোল" হয়ে গিয়েছিল "\u09b0\u09cb\u09b2". এটি ভুল নয় — JSON-এর নিয়মে যেকোনো অক্ষর \u দিয়ে লেখা যায়, আর পড়লে আবার আসল অক্ষর ফেরে। json module ডিফল্টে ensure_ascii=True রাখে, যাতে আউটপুটে শুধু ASCII অক্ষর থাকে — খুব পুরোনো, শুধু ইংরেজি বোঝা কোনো সিস্টেমও যেন ভাঙে না। কিন্তু বাংলার জন্য এর দাম অনেক: ফাইল খুলে মানুষ কিছুই পড়তে পারে না, আর আকারও বড় হয়। তিনটি বিকল্প ধাপে ধাপে দেখুন:
1city = {"জেলা": "ঢাকা", "কোড": 1000}2a = json.dumps(city)3b = json.dumps(city, ensure_ascii=False)4c = json.dumps(city, ensure_ascii=False, indent=2, sort_keys=True)5print(json.loads(a) == json.loads(b) == json.loads(c))
| a | |
| b |
- একটি ছোট dict. জেলার নাম আর পোস্টকোড, দুই চাবিই বাংলায়। এখনো কোনো JSON বানানো হয়নি।
- ডিফল্ট —
ensure_ascii=True. প্রতিটি বাংলা অক্ষর ছয় অক্ষরের\uXXXXহয়ে গেছে। কোনো মানুষ এটা পড়ে বুঝবে না “জেলা” লেখা আছে। ফাইলের আকার ৮৪ byte. ensure_ascii=False— বাংলা বাংলাই থাকে। এখন JSON-টি পড়ার মতো, আর আকার ৫১ byte — UTF-8-এ একটি বাংলা অক্ষর ৩ byte, escape করলে ৬. শর্ত একটাই: ফাইলে লেখার সময়encoding="utf-8"দিতে হবে (পর্ব ২৫), নইলে Windows-এর পুরোনো ডিফল্ট encoding-এUnicodeEncodeErrorহতে পারে।indent=2আরsort_keys=True. indent প্রতিটি স্তরকে নতুন লাইনে, ২টি ফাঁকা জায়গা সরিয়ে লেখে — config ফাইল বা ডিবাগিংয়ে মানুষের চোখের জন্য (pretty print). sort_keys চাবিগুলো সাজিয়ে লেখে (এখানে Unicode ক্রমে “ক” আগে) — তাতে একই ডেটা সবসময় হুবহু একই লেখা দেয়, আর git-এ দুই সংস্করণের পার্থক্য দেখা সহজ।- তিনটিই একই ডেটা।
json.loadsতিনটি থেকেই হুবহু একই dict ফেরত দেয় — বিকল্পগুলো শুধু লেখার ধরন বদলায়, ডেটা নয়। তাই নিয়ম: মানুষ পড়বে এমন ফাইলেensure_ascii=False, indent=2; নেটওয়ার্কে পাঠানোর জন্য indent ছাড়া, কারণ ফাঁকা জায়গাও byte খরচ করে।
যা JSON-এ যায় না — default= hook
একটি দোকানের বিক্রির রেকর্ডে তারিখ (datetime.date), পণ্যের set, বা টাকার জন্য Decimal (পর্ব ০৭) থাকা খুব স্বাভাবিক। কিন্তু JSON-এ তারিখ বা set বলে কোনো ধরন নেই, তাই json.dumps সরাসরি থেমে যায়:
>>> json.dumps({"date": date(2026, 1, 15)})
Traceback (most recent call last):
...
TypeError: Object of type date is not JSON serializable
সমাধান default= argument: একটি function দিন, যাকে json module ডাকবে যখনই এমন কোনো object পাবে যা সে নিজে চেনে না। আপনার function সেই object-কে JSON-এ যায় এমন কিছুতে বদলে দেবে — তারিখকে ISO 8601 ধাঁচের string "2026-01-15", set-কে সাজানো list. না চিনলে নিজেও TypeError তুলুন, যাতে অজানা ধরন চুপচাপ ভুলভাবে লেখা না হয়। মনে রাখবেন, এটি একমুখী: পড়ার সময় "2026-01-15" একটি সাধারণ str হয়েই আসবে; তারিখ চাইলে নিজে date.fromisoformat() দিয়ে বদলাতে হবে।
এবার সব একসাথে — পর্ব ২১-এর ধাঁচের ফল-ডেটা JSON-এ, একটি ফাইলে লিখে আবার পড়া, বাংলা ঠিক রেখে, আর default hook:
import json
from datetime import date
db = {"rahim": {"name": "Rahim", "marks": (78, 85, 90)}, 101: "রোল"}
back = json.loads(json.dumps(db))
print(json.dumps(db))
print(back == db, back["rahim"]["marks"], list(back))
city = {"জেলা": "ঢাকা", "থানা": ["মিরপুর", "উত্তরা"]}
with open("city.json", "w", encoding="utf-8") as f:
json.dump(city, f, ensure_ascii=False, indent=2)
with open("city.json", encoding="utf-8") as f:
loaded = json.load(f)
print(json.dumps(loaded, ensure_ascii=False), loaded == city)
def to_json(obj):
if isinstance(obj, date):
return obj.isoformat()
if isinstance(obj, set):
return sorted(obj)
raise TypeError(f"{type(obj).__name__} JSON-এ যায় না")
sale ={"date": date(2026, 1, 15), "items": {"ডাল", "চাল"}}
print(json.dumps(sale, default=to_json, ensure_ascii=False))প্রথম দুই লাইন round trip-এর stepper-টিই: {"rahim": {"name": "Rahim", "marks": [78, 85, 90]}, "101": "\u09b0\u09cb\u09b2"}, তারপর False [78, 85, 90] ['rahim', '101'] — tuple হলো list, 101 হলো '101', তাই আর সমান নয়। প্রথম লাইনেই দেখুন, ডিফল্ট ensure_ascii-এ “রোল” হয়ে গেছে \u… চিহ্নের সারি। এরপর json.dump একই ডেটা city.json-এ লিখল বাংলা অক্ষরে, ২ ঘর indent দিয়ে; json.load ফাইল থেকে পড়ে ফেরত দিল — {"জেলা": "ঢাকা", "থানা": ["মিরপুর", "উত্তরা"]} True: এখানে কোনো tuple বা int চাবি ছিল না, তাই round trip নিখুঁত। শেষ লাইনে default hook তারিখকে string আর set-কে সাজানো list বানিয়েছে: {"date": "2026-01-15", "items": ["চাল", "ডাল"]}. set-এর ক্রম নিশ্চিত নয়, তাই sorted — এতে আউটপুট প্রতিবার একই।
৭ · ভাঙা ফাইল, config, JSON Lines — আর নিরাপত্তা
ভাঙা JSON — json.JSONDecodeError
JSON-এর নিয়ম কড়া: শেষে বাড়তি কমা নিষেধ, একক উদ্ধৃতি নিষেধ, comment নিষেধ। হাতে সম্পাদনা করা config ফাইলে বা অর্ধেক লেখার পর বিদ্যুৎ চলে যাওয়া (load-shedding!) ফাইলে এমন ভুল খুব স্বাভাবিক। তখন json.loads বা json.load একটি json.JSONDecodeError তোলে, যা জানায় ঠিক কোন লাইনের কোন কলামে সমস্যা:
>>> json.loads('{"name": "রিয়া",}')
Traceback (most recent call last):
...
json.decoder.JSONDecodeError: Illegal trailing comma before end of object: line 1 column 17 (char 16)
>>> json.loads("{'name': 'রিয়া'}")
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
(বার্তার শব্দ Python version অনুযায়ী একটু আলাদা হতে পারে; বাড়তি কমার জন্য এই স্পষ্ট বার্তা Python 3.13 থেকে।) JSONDecodeError হলো ValueError-এর একটি subclass, আর এর lineno, colno আর msg attribute আছে। পর্ব ২৪-এ শিখেছেন exception কীভাবে ধরতে হয় — এখানে সেই জ্ঞান সরাসরি কাজে লাগে।
একটি দোকানের অ্যাপের config ফাইল
ধরুন একটি মুদি দোকানের বিলিং অ্যাপ বানাচ্ছেন। দোকানের নাম, VAT-এর হার, জেলা অনুযায়ী ডেলিভারি চার্জ — এগুলো কোডে লিখে রাখলে প্রতিবার বদলাতে প্রোগ্রামারকে ডাকতে হবে। ভালো নকশা: একটি config file, যেমন shop_config.json, যা দোকানি নিজে সম্পাদনা করতে পারেন। কিন্তু মানুষ সম্পাদনা করবে মানেই ফাইল হারাতে বা ভাঙতে পারে। তাই config পড়ার কোডে তিনটি পরিস্থিতি সামলাতে হয়:
import json
from pathlib import Path
DEFAULTS = {"shop": "আমার দোকান", "vat": 0.05, "delivery": {"ঢাকা": 60}}
def load_config(path="shop_config.json"):
try:
with open(path, encoding="utf-8") as f:
user = json.load(f)
except FileNotFoundError:
return DEFAULTS # ফাইল নেই: ডিফল্ট দিয়ে চলুক
except json.JSONDecodeError as e:
raise SystemExit(f"{path}: লাইন {e.lineno}, কলাম {e.colno} — {e.msg}")
if not isinstance(user, dict):
raise SystemExit(f"{path}: ভেতরে একটি JSON object থাকতে হবে")
return DEFAULTS | user # ফাইলের মান ডিফল্টের ওপরে বসেলক্ষ করুন সিদ্ধান্তগুলো: ফাইল না থাকা স্বাভাবিক (প্রথমবার অ্যাপ চালানো) — তাই ডিফল্ট দিয়ে চলা। কিন্তু ফাইল ভাঙা থাকলে চুপচাপ ডিফল্টে চলে যাওয়া বিপজ্জনক — দোকানি ভাববেন তাঁর বদলানো VAT কাজ করছে, অথচ করছে না। তাই স্পষ্ট বার্তা দিয়ে থামানো, লাইন আর কলামসহ। তৃতীয়ত, বৈধ JSON মানেই সঠিক ডেটা নয় — ফাইলে শুধু [1, 2] থাকলেও তা বৈধ JSON, তাই ধরন যাচাই। শেষে পর্ব ১৬-এর | দিয়ে মেশানো: ফাইলে যা আছে তা ডিফল্টের ওপরে বসে, যা নেই তা ডিফল্ট থেকে আসে। (এই মেশানো শুধু ওপরের স্তরে; “delivery”-র ভেতরের dict পুরোটাই বদলে যায়।)
JSON Lines — প্রতি লাইনে একটি রেকর্ড
একটি সাধারণ JSON ফাইল পুরোটা মিলে একটি মান — সাধারণত একটি বড় list. এতে দুটি সমস্যা: নতুন একটি রেকর্ড যোগ করতে পুরো ফাইল পড়ে আবার লিখতে হয়, আর কোটি রেকর্ডের ফাইল একসাথে মেমরিতে আনতে হয়। JSON Lines (ফাইলের শেষে সাধারণত .jsonl) একটি সহজ সমাধান: প্রতিটি লাইনে আলাদা একটি সম্পূর্ণ JSON মান। লেখা মানে ফাইলের শেষে "a" mode-এ একটি লাইন যোগ — f.write(json.dumps(rec, ensure_ascii=False) + "\n"). পড়া মানে লাইন ধরে লুপ, প্রতিটি লাইনে json.loads. একটি লাইন ভাঙলে শুধু সেটিই নষ্ট, বাকিগুলো ঠিক থাকে। log, বিক্রির ধারাবাহিক রেকর্ড আর AI মডেলের প্রশিক্ষণের ডেটাসেটে এই ফরম্যাট খুব প্রচলিত। (এর জন্য JSON লেখায় indent দেওয়া যাবে না — তাহলে একটি রেকর্ড অনেক লাইনে ছড়িয়ে যাবে।)
নিরাপত্তা — json নিরাপদ, pickle আর eval নয়
বাইরের ডেটা — ইন্টারনেট থেকে, ব্যবহারকারীর আপলোড থেকে, অন্য কারও পাঠানো ফাইল থেকে — সবসময় অবিশ্বস্ত ধরুন। এখানে json-এর একটি বড় গুণ: json.loads শুধু ছয় ধরনের মান বানাতে পারে (dict, list, str, int/float, bool, None). যত অদ্ভুত লেখাই দিন, সে কোনো কোড চালায় না, কোনো function ডাকে না। (তবু অনেক বড় বা অনেক গভীর ডেটা দিয়ে মেমরি ভরানো যায়, তাই সার্ভারে আকারের সীমা রাখা আর পড়ার পর চাবি ও ধরন যাচাই করা দরকার।)
Python-এর আরেকটি module আছে — pickle — যা প্রায় যেকোনো Python object (নিজের class, set, তারিখ সহ) byte-এ বদলে আবার ফেরাতে পারে। শুনতে json-এর চেয়ে শক্তিশালী, আর নিজের প্রোগ্রামের নিজের ডেটা সাময়িক রাখার জন্য কাজেরও। কিন্তু Python-এর ডকুমেন্টেশন বড় করে সতর্ক করে: pickle নিরাপদ নয়। একটি pickle ফাইল আসলে পুনর্গঠনের নির্দেশের তালিকা, আর সেই নির্দেশে “এই function-টি এই argument দিয়ে ডাকো” থাকতে পারে। তাই একটি বিশেষভাবে বানানো pickle ফাইল খোলার মুহূর্তেই আপনার কম্পিউটারে যেকোনো কোড চলতে পারে — একে বলে arbitrary code execution. নিয়ম সোজা: অবিশ্বস্ত উৎসের ডেটা কখনো pickle.load করবেন না। বাইরের সাথে ডেটা বদলের জন্য JSON.
একই কারণে JSON দেখতে Python dict-এর মতো বলে eval(text) দিয়ে পড়ার চিন্তাও বাদ দিন। পর্ব ১০-এ দেখেছেন, eval লেখাকে Python কোড হিসেবে চালায় — অর্থাৎ JSON-এর জায়গায় কেউ কোড পাঠালে তা চলবে, এক ধরনের injection. তা ছাড়া বৈধ JSON-এর true/null Python-এ নামই নয়, তাই eval প্রায়ই NameError দেয়। JSON-এর জন্য একমাত্র সঠিক রাস্তা json.loads.
৮ · কোনটি কখন — CSV, JSON, নাকি database
এখন দুটি ফরম্যাটই জানেন। বাস্তব প্রশ্ন হলো, নিজের প্রজেক্টে কোনটি নেবেন? নিচের টেবিলটি একটি শুরু:
| প্রশ্ন | CSV | JSON | database (যেমন sqlite3) |
|---|---|---|---|
| ডেটার আকার | সমতল টেবিল | nested, ঐচ্ছিক ঘর | অনেক টেবিল, সম্পর্কসহ |
| কে খুলবে | Excel/Sheets-এ মানুষ | প্রোগ্রাম, API, config সম্পাদক | প্রোগ্রাম |
| ধরন রাখে? | না — সব str | কিছুটা: সংখ্যা, bool, null | হ্যাঁ, কলাম অনুযায়ী |
| বড় ডেটায় খোঁজা | পুরো ফাইল পড়তে হয় | পুরো ফাইল পড়তে হয় | index দিয়ে দ্রুত, query দিয়ে |
| একসাথে কয়েকজন লিখলে | ঝুঁকি | ঝুঁকি | নিরাপদে সামলায় |
| সাধারণ উদাহরণ | মার্কশিট, বিক্রির রিপোর্ট | অ্যাপের সেটিংস, API-র উত্তর | দোকানের সব বিক্রি, বছরের পর বছর |
মোটা দাগে: মানুষ টেবিল হিসেবে দেখবে বা Excel-এ খুলবে — CSV. ভেতরে list বা ভেতরের রেকর্ড আছে, বা অন্য প্রোগ্রাম/API-র সাথে কথা — JSON. ডেটা বড় হচ্ছে, বারবার “এই মাসে ঢাকায় কত বিক্রি?” ধরনের প্রশ্ন করবেন, বা একসাথে কয়েকজন লিখবে — তখন ফাইল ছেড়ে database. সুখবর: Python-এর standard library-তেই sqlite3 আছে, একটি পুরো database যা একটি মাত্র ফাইলে থাকে — পরের পর্বের লাইব্রেরি ভ্রমণে তার একটি ঝলক দেখবেন।
সামনে যা আসছে — API-র JSON আর বড় টেবিল
পর্ব ৪৭-এ যখন ইন্টারনেটের API থেকে ডেটা আনবেন — আবহাওয়া, মুদ্রার হার, কোনো সরকারি খোলা ডেটা — উত্তর প্রায় সবসময় আসবে JSON-এ, প্রায়ই অনেক স্তরে nested: data["results"][0]["location"]["district"]. এই পর্বের সব শিক্ষা সেখানে লাগবে: কোন চাবি সবসময় থাকবে না (get, পর্ব ১৬), কোন সংখ্যা str হয়ে এসেছে, আর ভাঙা উত্তরে JSONDecodeError. আর পর্ব ৪৬-এ দেখবেন pandas, যার pd.read_csv("sales.csv") এক লাইনে লাখো সারির CSV পড়ে, কলামের ধরন নিজে অনুমান করে, আর হিসাব করে অনেক দ্রুত। তবে pandas-ও ভেতরে একই সমস্যাগুলো সামলায় — delimiter, encoding, ফাঁকা ঘর — তাই আজকের ভিত্তি না জানলে pandas-এর ভুলও ধরতে পারবেন না।
C বা C++-এর standard library-তে CSV বা JSON-এর কোনো parser নেই। C-তে অনেকে strtok(line, ",") দিয়ে CSV ভাগ করেন — যা হুবহু split(",")-এর মতো উদ্ধৃতির ভেতরের কমায় ভাঙে, আর পাশাপাশি দুটি কমা (ফাঁকা ঘর) পুরো বাদ দেয়। C++-এ JSON-এর জন্য সাধারণত বাইরের লাইব্রেরি লাগে। Python-এ দুটিই standard library-তে, পরীক্ষিত — এটাই বড় পার্থক্য। ফাইল খোলা আর text বনাম binary mode-এর C-দিক দেখুন C কোর্সের ফাইল I/O পর্বে।
json.dumpআরjson.dumps-এর পার্থক্য? —dumpsJSON লেখাটি str হিসেবে ফেরত দেয়;dumpসরাসরি একটি খোলা ফাইলে লেখে। একইভাবেloads(string থেকে) আরload(ফাইল থেকে)।- CSV ফাইল কেন
newline=""দিয়ে খুলতে হয়? — csv module লাইন-শেষ নিজে সামলায়; না দিলে Windows-এ প্রতিটি সারির পরে ফাঁকা সারি আসে আর উদ্ধৃতির ভেতরের লাইন-ভাঙা ভুল পড়া হতে পারে। json.loads(json.dumps({1: (2, 3)}))-এর ফল? —{'1': [2, 3]}; JSON চাবি সবসময় string, আর tuple হয়ে যায় list.- বাংলা লেখা JSON-এ
\u09..হয়ে যাচ্ছে কেন? — ডিফল্টensure_ascii=True;ensure_ascii=Falseদিন আর ফাইলencoding="utf-8"দিয়ে খুলুন। - pickle কেন অবিশ্বস্ত ডেটায় বিপজ্জনক? — pickle ফাইল খোলার সময় যেকোনো function ডাকা যায়, তাই বানানো ফাইল কোড চালাতে পারে; JSON শুধু ডেটা বানায়।
৯ · AI-এর যুগে — AI-এর লেখা CSV/JSON কোড যাচাই
“এই CSV পড়ে গড় বের করো” বা “ডেটা JSON-এ সেভ করো” — AI এমন কোড সেকেন্ডে লিখে দেয়, আর ছোট ইংরেজি নমুনায় সেটি চলেও। সমস্যাগুলো বেরোয় আসল ডেটায়: বাংলা নাম, ঠিকানায় কমা, Excel-এর ফাইল, Windows. নিচের ভুলগুলো বারবার দেখা যায়:
| AI প্রায়ই যা লেখে | সমস্যা | ভালো রূপ |
|---|---|---|
line.strip().split(",") | উদ্ধৃতির ভেতরের কমা আর লাইন-ভাঙায় কলাম সরে যায়, চুপচাপ | csv.reader / DictReader |
open("out.csv", "w") | Windows-এ ফাঁকা সারি; encoding না দেওয়ায় বাংলায় error | newline="", encoding="utf-8" (Excel-এর জন্য utf-8-sig) |
row["math"] + row["bangla"] | string জোড়া লাগে, যোগ হয় না | পড়ামাত্র int()/float(), ফাঁকা ঘর সামলানো |
json.dump(data, f) | বাংলা হয় \u09.., ফাইল পড়া যায় না | ensure_ascii=False, indent=2 |
pickle দিয়ে ডেটা সেভ বা আপলোড পড়া | অবিশ্বস্ত ফাইলে কোড চলতে পারে; অন্য ভাষা পড়তে পারে না | JSON |
eval(text) বা str(d) দিয়ে JSON | কোড injection; অবৈধ JSON | json.loads / json.dumps |
| JSON পড়ার কোডে কোনো try নেই | ভাঙা ফাইলে প্রোগ্রাম ভেঙে পড়ে, অস্পষ্ট বার্তা | JSONDecodeError ধরে লাইন-কলামসহ বার্তা |
AI-কে CSV বা JSON নিয়ে কোড লিখতে বলার সময় শর্তগুলো আগেই বলে দিন:
AI-এর লেখা CSV/JSON কোড পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১CSV কয়েক দশক ধরে ব্যবহার হচ্ছে, অথচ এর একটি লিখিত বর্ণনা (RFC 4180) এসেছে মাত্র ২০০৫ সালে — আর সেটিও কড়া মানদণ্ড নয়। এই “মানদণ্ডহীনতা” বাস্তবে কী কী যন্ত্রণা তৈরি করে, আর Python-এর csv module তা কীভাবে সামলায়?
কমা দিয়ে আলাদা করা মানের তালিকা কম্পিউটিংয়ের খুব শুরুর দিক থেকেই ছিল — ১৯৭০-এর দশকের প্রোগ্রামিং টুলেও এমন ইনপুট পড়ার ব্যবস্থা দেখা যায়। পরে spreadsheet (VisiCalc, Lotus 1-2-3, তারপর Excel) আর database টুলগুলো ডেটা export-import করতে এই সরল ধারণা নেয়। কিন্তু কেউ একটি কেন্দ্রীয় নিয়ম লেখেনি। প্রতিটি প্রোগ্রাম নিজের মতো সিদ্ধান্ত নিল: ঘরের ভেতরে কমা থাকলে কী করব? উদ্ধৃতি চিহ্ন কী দিয়ে escape করব — "" নাকি \"? লাইন শেষ \r\n নাকি \n? প্রথম লাইন হেডার কি না? encoding কী?
২০০৫ সালের অক্টোবরে Yakov Shafranovich-এর লেখা RFC 4180 প্রকাশিত হয়। এটি “Informational” ধরনের RFC — অর্থাৎ নতুন নিয়ম চাপিয়ে দেওয়া নয়, বরং বেশিরভাগ প্রোগ্রাম (মূলত Excel) যা করে তার বর্ণনা, আর text/csv MIME type নিবন্ধন। এতে বলা হলো: সারি শেষ CRLF দিয়ে; কমা, উদ্ধৃতি বা লাইন-ভাঙা থাকলে ঘর দ্বিগুণ উদ্ধৃতিতে; ভেতরের উদ্ধৃতি দুবার লিখে। কিন্তু encoding নিয়ে প্রায় কিছু বলা হয়নি, আর ততদিনে কোটি কোটি ফাইল অন্য নিয়মে লেখা হয়ে গেছে।
ফলে বাস্তব যন্ত্রণা: ইউরোপের Excel সেমিকোলন দেয়, কারণ সেখানে কমা দশমিক চিহ্ন; কোনো সিস্টেম \" দিয়ে escape করে; পুরোনো Excel আঞ্চলিক encoding-এ সেভ করে বাংলা নষ্ট করে; কেউ BOM দেয়, কেউ দেয় না; কেউ সংখ্যার শুরুর 0 মুছে দেয় (মোবাইল নম্বর 01711… হয়ে যায় 1711…)। একটি “CSV ফাইল” পেলে আপনি আসলে জানেন না তার নিয়ম কী।
Python-এর csv module (PEP 305, Python 2.3 থেকে, ২০০৩) এই বাস্তবতা মেনে নিয়ে বানানো। এর কেন্দ্রে dialect ধারণা: delimiter, quotechar, escapechar, lineterminator, doublequote ইত্যাদি সেটিংয়ের একটি গুচ্ছ। ডিফল্ট dialect "excel" — কারণ সবচেয়ে বেশি ফাইল Excel থেকেই আসে; সাথে আছে "excel-tab" আর "unix". প্রতিটি সেটিং আলাদাভাবে বদলানো যায়, নিজের dialect নিবন্ধন করা যায়, আর csv.Sniffer নমুনা দেখে dialect অনুমান করতে পারে। অর্থাৎ module একটি “সঠিক” CSV চাপিয়ে দেয় না, বরং বলে — “আপনার ফাইলের নিয়মটা বলুন, পড়ে দিচ্ছি।”
মূল উপলব্ধি: যে ফরম্যাটের কোনো মালিক বা কড়া মানদণ্ড নেই, তার “সরলতা” আসলে প্রতিটি পাঠকের ঘাড়ে জটিলতা চাপিয়ে দেয়। তাই বাইরের CSV পেলে আগে কয়েকটি লাইন খুলে নিয়ম দেখে নিন, তারপর কোড লিখুন — আর নিজে লিখলে RFC 4180-এর পথে থাকুন।
প্র ০২JSON এসেছে ২০০০-এর দশকের শুরুতে, অথচ তখন XML ছিল ডেটা আদান-প্রদানের প্রতিষ্ঠিত মানদণ্ড। ওয়েব API-র জগতে JSON কেন XML-কে ছাড়িয়ে গেল?
১৯৯৮ সালে W3C XML 1.0 প্রকাশ করে, আর পরের কয়েক বছরে XML হয়ে ওঠে “সবকিছুর ফরম্যাট”: config ফাইল, office document, আর SOAP-এর মতো ওয়েব সার্ভিসের প্রোটোকল। XML শক্তিশালী — schema দিয়ে কড়া যাচাই, namespace, attribute — কিন্তু ভারী। একটি ছোট রেকর্ড লিখতে প্রতিটি মানের দুই পাশে খোলা ও বন্ধ ট্যাগ, আর প্রোগ্রামে পড়তে DOM বা SAX parser, তারপর গাছ ধরে হেঁটে মান বের করা।
২০০১ সালের দিকে Douglas Crockford ও তাঁর সহকর্মীরা ব্রাউজার আর সার্ভারের মধ্যে ডেটা পাঠাতে JavaScript-এর object লেখার ধরন ব্যবহার করছিলেন। Crockford নিজে বলেন, তিনি JSON “আবিষ্কার” করেননি, “খুঁজে পেয়েছেন” — ধাঁচটি JavaScript-এ আগে থেকেই ছিল; তিনি একটি ছোট, কড়া উপসেট বেছে নাম দেন আর json.org-এ এক পাতার বর্ণনা প্রকাশ করেন। পুরো ব্যাকরণ এক পাতায় আঁটে — XML-এর বিশাল স্পেসিফিকেশনের তুলনায় এটি বড় আকর্ষণ। পরে এটি RFC 4627 (২০০৬), তারপর ECMA-404 (২০১৩) আর RFC 8259 (২০১৭) হিসেবে মানদণ্ড পায়; RFC 8259 বলে, খোলা আদান-প্রদানে JSON-কে UTF-8-এ লিখতে হবে।
JSON কেন জিতল? প্রথমত, ডেটা মডেলের মিল: প্রায় সব আধুনিক ভাষায় dict/map আর list/array আছে, তাই JSON পড়লে সরাসরি পরিচিত ধরন পাওয়া যায় — XML-এর গাছ থেকে নিজে টেনে বের করতে হয় না। দ্বিতীয়ত, ব্রাউজার: ২০০৫ সালে “Ajax” শব্দটি জনপ্রিয় হয় (মজার ব্যাপার, এর X মানে XML), কিন্তু JavaScript-এ JSON পড়া অনেক সহজ ছিল। প্রথম দিকে মানুষ eval() দিয়ে JSON পড়ত — যা ছিল নিরাপত্তার দুঃস্বপ্ন; ২০০৯ সালের ECMAScript 5-এ নিরাপদ JSON.parse আসে। তৃতীয়ত, আকার আর পড়ার সহজতা। REST ধাঁচের API আর মোবাইল অ্যাপের যুগে এই সুবিধাগুলো নির্ণায়ক হয়ে দাঁড়ায়।
তবে JSON-এর সরলতার দামও আছে। এতে comment নেই — Crockford ইচ্ছে করে বাদ দেন, কারণ তিনি দেখেছিলেন কেউ কেউ comment-এ parser-এর জন্য নির্দেশ লুকিয়ে রাখছিল, যা আদান-প্রদানকে ভাঙত। তারিখ, binary ডেটা বা বড় দশমিকের জন্য আলাদা ধরন নেই। schema যাচাইয়ের জন্য পরে আলাদা মানদণ্ড (JSON Schema) লাগে। XML আজও document আর কিছু শিল্পে (office ফাইলের ভেতরে, কিছু সরকারি ও আর্থিক ব্যবস্থায়) ব্যাপক।
মূল উপলব্ধি: প্রযুক্তির লড়াইয়ে প্রায়ই সবচেয়ে শক্তিশালী নয়, সবচেয়ে সহজে গ্রহণযোগ্যটি জেতে — যেটি প্রোগ্রামারের মনের ডেটা মডেলের সাথে মেলে আর এক বিকেলে শেখা যায়। তবে যা বাদ দেওয়া হয়েছিল (comment, তারিখ, schema), তার অভাব পরে অন্য উপায়ে মেটাতে হয়।
প্র ০৩Python dict-এ int, tuple — যেকোনো hashable জিনিস চাবি হতে পারে, অথচ JSON object-এর চাবি শুধু string. এই সীমা কেন? আর এটি না জানলে কোন কোন বাগ হয়?
কারণটি JSON-এর জন্মে। JavaScript-এ সাধারণ object-এর property-র নাম সবসময় string — obj[1] লিখলেও JavaScript ভেতরে চাবিকে "1" বানিয়ে নেয়। JSON যেহেতু JavaScript-এর object লেখার একটি উপসেট, তাই সেই নিয়মই এসেছে। তার ওপর JSON-কে হতে হয়েছে ভাষা-নিরপেক্ষ. সব ভাষার map যেকোনো ধরনের চাবি নেয় না; কিন্তু string চাবি সব ভাষা বোঝে। একটি জটিল চাবি (যেমন Python-এর tuple) অন্য ভাষায় কীভাবে ধরা হবে, তার কোনো সর্বজনীন উত্তর নেই — তাই JSON সরলতম, সবার পক্ষে সম্ভব পথটি নেয়।
Python-এর json module এখানে একটু নমনীয়: str ছাড়াও int, float, bool আর None চাবিকে লেখার সময় নিজেই string বানায় — 1 হয় "1", True হয় "true", None হয় "null". কিন্তু tuple চাবি পেলে সে থামে: TypeError: keys must be str, int, float, bool or None, not tuple. (skipkeys=True দিলে এমন চাবি চুপচাপ বাদ পড়ে — সাধারণত আরও খারাপ।)
এখান থেকে তিনটি প্রচলিত বাগ। এক, round trip-এ int চাবি হারানো: রোল নম্বর দিয়ে চাবি করা dict সেভ করে আবার পড়লে marks[101] দেয় KeyError, কারণ চাবি এখন "101". দুই, চাবির সংঘর্ষ: {1: "a", "1": "b"} Python-এ দুটি আলাদা চাবি, কিন্তু json.dumps লেখে {"1": "a", "1": "b"} — একই চাবি দুবার! পড়ার সময় Python শেষেরটি রাখে, একটি মান নিঃশব্দে হারায়। তিন, সাজানোর পার্থক্য: sort_keys=True string হিসেবে সাজায়, তাই "10" আসে "9"-এর আগে।
সমাধানের কয়েকটি উপায়। সবচেয়ে সহজ: JSON-এ রাখার ডেটার নকশাতেই চাবি string রাখুন, বা dict-এর বদলে list of records ব্যবহার করুন — [{"roll": 101, "mark": 78}] — যেখানে রোল একটি মান, তাই int থাকে। পড়ার পর ফেরাতে চাইলে {int(k): v for k, v in data.items()}. আরও নিয়ন্ত্রণ চাইলে json.load-এর object_hook বা object_pairs_hook argument দিয়ে প্রতিটি object তৈরির সময় নিজের রূপান্তর চালানো যায়; শেষেরটি দিয়ে ডুপ্লিকেট চাবি ধরাও সম্ভব।
মূল উপলব্ধি: serialization মানে দুটি ধরন-ব্যবস্থার মধ্যে অনুবাদ, আর অনুবাদে সবসময় কিছু হারায়। কোন জিনিসটি হারাবে — tuple, int চাবি, তারিখ — তা আগে থেকে জানুন, আর ডেটার নকশা এমন করুন যাতে হারানোর মতো কিছু না থাকে।
প্র ০৪pickle প্রায় যেকোনো Python object সেভ করতে পারে, অথচ ডকুমেন্টেশন বলে “অবিশ্বস্ত ডেটা কখনো unpickle করবেন না”. একটি ডেটা ফাইল খোলা কীভাবে কোড চালানোর সমান হয়ে যায়? আর বাস্তবে এর প্রভাব কোথায় দেখা গেছে?
JSON শুধু “ডেটা কেমন” বর্ণনা করে — এই চাবি, এই মান। pickle এর চেয়ে অনেক বেশি পারে, কারণ সে বর্ণনা করে “object-টি কীভাবে আবার বানাতে হবে”. একটি pickle আসলে একটি ছোট stack-ভিত্তিক যন্ত্রের জন্য নির্দেশের সারি (opcode). নিজের class-এর object সেভ করতে pickle জিজ্ঞেস করে: “আপনাকে আবার বানাতে কোন function, কোন argument দিয়ে ডাকতে হবে?” — object এর উত্তর দেয় __reduce__ নামের একটি method দিয়ে। খোলার সময় pickle.load সেই নির্দেশ মেনে function-টি import করে আর ডাকে। এই নমনীয়তাই pickle-কে শক্তিশালী করে।
সমস্যা হলো, নির্দেশটি কে লিখেছে, pickle তা যাচাই করে না, আর কোন function ডাকা যাবে তার কোনো সীমা নেই। যে কেউ হাতে এমন একটি pickle ফাইল বানাতে পারে, যার নির্দেশে লেখা “এই module-এর এই function-টি এই argument দিয়ে ডাকো” — আর সেই function হতে পারে ফাইল মোছা বা অন্য প্রোগ্রাম চালানোর মতো কিছু। আপনি শুধু ফাইলটি “খুললেন”, অথচ ক্ষতিকর কাজ ততক্ষণে হয়ে গেছে — কোনো object ব্যবহার করার আগেই। নিরাপত্তার ভাষায় এটি arbitrary code execution. এটি কোনো বাগ নয় যা ঠিক করা যাবে; এটি pickle-এর নকশারই অংশ। তাই Python-এর ডকুমেন্টেশন pickle পাতার শুরুতেই সতর্কবার্তা দেয়, আর বিকল্প হিসেবে JSON-এর কথা বলে।
বাস্তব প্রভাব সবচেয়ে বেশি দেখা গেছে মেশিন লার্নিংয়ে। বহু বছর ধরে প্রশিক্ষিত মডেল সেভ করার প্রচলিত উপায় ছিল pickle-ভিত্তিক ফাইল (যেমন .pkl, আর PyTorch-এর পুরোনো torch.load-এর ডিফল্ট আচরণ)। মানুষ ইন্টারনেটের মডেল-শেয়ারিং সাইট থেকে অন্যের মডেল নামিয়ে খোলে — আর নিরাপত্তা গবেষকরা এমন মডেল ফাইলও খুঁজে পেয়েছেন যেগুলো খোলার সময় ক্ষতিকর কোড চালাত। এর জবাবে এসেছে শুধু-ডেটা ফরম্যাট (যেমন safetensors, যেখানে শুধু সংখ্যার সারি থাকে, কোনো নির্দেশ নয়), আর PyTorch সাম্প্রতিক version-এ torch.load-এর ডিফল্ট বদলে শুধু weight পড়ার নিরাপদ পথে নিয়েছে।
pickle তবু অকেজো নয়: নিজের প্রোগ্রামের নিজের বানানো ডেটা, নিজের কম্পিউটারে সাময়িক রাখা, বা একই প্রোগ্রামের process-গুলোর মধ্যে ডেটা পাঠানো (পর্ব ৪১-এর multiprocessing ভেতরে এটাই ব্যবহার করে) — এসব জায়গায় সে কাজের। সীমারেখা একটাই: ফাইলটি কে বানিয়েছে, আপনি কি তাকে নিজের কম্পিউটারে কোড চালানোর অধিকার দিতে রাজি? না হলে pickle নয়। একই যুক্তি eval-এর ক্ষেত্রেও, আর YAML-এর অনিরাপদ loader-এর ক্ষেত্রেও (প্রশ্ন ০৫)।
মূল উপলব্ধি: যে ফরম্যাট “নির্দেশ” বহন করতে পারে, তা খোলা মানে প্রেরকের কোড চালানো। নিরাপদ আদান-প্রদানের জন্য এমন ফরম্যাট বেছে নিন যা শুধু ডেটা বহন করে — যেখানে সবচেয়ে খারাপ ক্ষেত্রেও আপনি পাবেন ভুল ডেটা, কোনো চলমান কোড নয়।
প্র ০৫config ফাইলের জন্য JSON ছাড়াও YAML আর TOML খুব জনপ্রিয় — Python-এর নিজের pyproject.toml-ও TOML-এ। এই ফরম্যাটগুলো কেন এল, আর কখন কোনটি বেছে নেবেন?
JSON যন্ত্রের জন্য দারুণ, কিন্তু মানুষের হাতে সম্পাদনার জন্য বিরক্তিকর: প্রতিটি চাবিতে উদ্ধৃতি, শেষে কমা দিলেই error, আর comment লেখার উপায় নেই — অথচ config ফাইলে “এই সংখ্যা কেন 30” লিখে রাখা খুব দরকারি। এই অভাব থেকে দুটি জনপ্রিয় বিকল্প এসেছে।
YAML প্রথম প্রস্তাবিত হয় ২০০১ সালে (Clark Evans, Ingy döt Net ও Oren Ben-Kiki). এটি indentation দিয়ে গঠন বোঝায় — Python-এর মতোই — আর উদ্ধৃতি প্রায় লাগে না, comment চলে। Docker Compose, Kubernetes, GitHub Actions-এর মতো DevOps টুলে YAML সর্বত্র। YAML 1.2 (২০০৯) এমনভাবে সাজানো যে প্রায় সব বৈধ JSON-ই বৈধ YAML. কিন্তু এর নমনীয়তার দামও বিখ্যাত। পুরোনো YAML 1.1 নিয়মে yes, no, on, off উদ্ধৃতি ছাড়া লিখলে bool হয়ে যায় — দেশের কোডের তালিকায় নরওয়ের NO হয়ে যেত false, যা “Norway problem” নামে পরিচিত। আর নিরাপত্তা: Python-এর জনপ্রিয় PyYAML লাইব্রেরির পুরোনো yaml.load ফাইলের নির্দেশে Python object বানাতে পারত — pickle-এর মতোই বিপদ। তাই নিয়ম: সবসময় yaml.safe_load.
TOML (Tom's Obvious, Minimal Language) ২০১৩ সালে প্রস্তাব করেন GitHub-এর সহ-প্রতিষ্ঠাতা Tom Preston-Werner, আর ১.০ সংস্করণ আসে ২০২১ সালে। এর লক্ষ্য স্পষ্টতা: পুরোনো INI ফাইলের মতো [section] আর key = value, কিন্তু কড়া ধরনসহ — string, সংখ্যা, bool, তারিখ-সময় আলাদা করে চেনা যায়, আর YAML-এর মতো অনুমান-নির্ভর রূপান্তর নেই। Python প্রজেক্টের কেন্দ্রীয় সেটিং ফাইল pyproject.toml (PEP 518, ২০১৬; পর্ব ২৩) TOML-এ, আর Rust-এর Cargo.toml-ও। Python 3.11 থেকে standard library-তে tomllib আছে (PEP 680) — তবে সেটি শুধু পড়তে পারে, লিখতে পারে না; লেখার জন্য বাইরের লাইব্রেরি লাগে। যুক্তি: config ফাইল সাধারণত মানুষ লেখে, প্রোগ্রাম পড়ে।
কখন কোনটি? প্রোগ্রাম-থেকে-প্রোগ্রাম ডেটা আর API — JSON, কারণ সব ভাষা সমানভাবে বোঝে আর নিয়ম কড়া। মানুষ সম্পাদনা করবে এমন সহজ, সমতল config — TOML; Python প্রজেক্টে তো এটিই প্রচলিত। গভীরভাবে nested config, বা যে টুল নিজেই YAML চায় (CI, Kubernetes) — YAML, সাবধানে। আর standard library-র configparser দিয়ে পুরোনো INI ফাইলও পড়া যায়।
মূল উপলব্ধি: প্রতিটি ফরম্যাট একটি আপস — যন্ত্রের জন্য কড়া নিয়ম বনাম মানুষের জন্য আরাম, নমনীয়তা বনাম চমকহীনতা। প্রশ্ন করুন “ফাইলটি কে লিখবে, কে পড়বে?” — উত্তরই ফরম্যাট ঠিক করে দেয়।
অনুশীলন
এগুলোর বেশিরভাগে নতুন কোড লিখতে হবে না — অনুমান করা, হাতে চালানো আর মিলিয়ে নেওয়াই আসল কাজ।
-
হাতে চালান: চালানোর আগে লিখুন কী ছাপা হবে — (ক)
print(json.dumps({"ok": True, "x": None, "t": (1, 2)})); (খ)print(json.loads('{"5": [1, 2]}')); (গ)print(len("a,\"b,c\",d".split(",")))আরprint(next(csv.reader(['a,"b,c",d']))).(ক)
{"ok": true, "x": null, "t": [1, 2]}— True হয়true, None হয়null, tuple হয় array. (খ){'5': [1, 2]}— চাবি str-ই থাকে, Python নিজে int বানায় না; এটি dict হিসেবে ছাপা হয়েছে বলে একক উদ্ধৃতি। (গ) প্রথমটি4('a','"b','c"','d'), দ্বিতীয়টি['a', 'b,c', 'd']— ৩টি ঘর। -
ফরম্যাট বাছাই করুন: প্রতিটির জন্য CSV, JSON, JSON Lines নাকি database — আর কেন: (ক) ৬৪ জেলার নাম, বিভাগ আর আয়তনের তালিকা, যা একজন সাংবাদিক Excel-এ খুলবেন; (খ) একটি মোবাইল অ্যাপের সেটিংস — ভাষা, থিম, আর প্রিয় জেলার list; (গ) একটি সার্ভারের প্রতিটি অনুরোধের log, দিনে লাখো লাইন; (ঘ) একটি সুপারশপের পাঁচ বছরের সব বিক্রি, যেখান থেকে ম্যানেজার যেকোনো সময় “গত মাসে চট্টগ্রামে কত” জানতে চান।
(ক) CSV — সমতল টেবিল, মানুষ Excel-এ খুলবে; বাংলা নামের জন্য
utf-8-sigদিয়ে লিখুন। (খ) JSON — ভেতরে একটি list আছে, প্রোগ্রাম পড়বে;ensure_ascii=False, indent=2. (গ) JSON Lines — প্রতিটি অনুরোধ একটি লাইন, ফাইলের শেষে যোগ করা সহজ, একটি লাইন ভাঙলে বাকিগুলো নিরাপদ। (ঘ) database (যেমন sqlite3) — বড় ডেটা, বারবার নির্দিষ্ট প্রশ্ন; প্রতিবার পুরো ফাইল পড়া অচল। -
বাগ খুঁজুন: একটি AI এই কোড দিয়েছে বিক্রির হিসাব Excel-এর জন্য লিখতে:
f = open("sales.csv", "w"), তারপর প্রতিটি পণ্যের জন্যf.write(name + "," + str(qty) + "\n"). পণ্যের নাম বাংলায়, যেমনচাল, মিনিকেট. অন্তত চারটি সমস্যা খুঁজুন।(১) নামে কমা থাকলে হাতে জোড়া লাগানো লাইনে উদ্ধৃতি নেই — Excel “চাল” আর “মিনিকেট” দুই কলামে ভাগ করবে;
csv.writerনিজে উদ্ধৃতি বসাত। (২)encodingনেই — Windows-এ পুরোনো ডিফল্ট encoding-এ বাংলা লিখতে গিয়েUnicodeEncodeErrorহতে পারে; আর Excel-এ ঠিক দেখাতেutf-8-sigদরকার। (৩)newline=""নেই — csv.writer-এ বদলালে এটিও লাগবে। (৪)withছাড়া খোলা, close নেই — error হলে ফাইল অসম্পূর্ণ থাকতে পারে (পর্ব ২৫)। বোনাস: হেডার লাইন নেই। -
ছোট কোড লিখুন: একটি function
save_results(db, path)আরload_results(path)লিখুন, যা পর্ব ২১-এর ধাঁচেরdb(নাম →{"name": ..., "marks": [...]}) JSON-এ সেভ করে আর পড়ে। শর্ত: বাংলা পড়ার মতো থাকবে; ফাইল না থাকলে খালি dict; ভাঙা ফাইলে লাইন-কলামসহ বার্তা দিয়েNone.import json def save_results(db, path): with open(path, "w", encoding="utf-8") as f: json.dump(db, f, ensure_ascii=False, indent=2) def load_results(path): try: with open(path, encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return {} except json.JSONDecodeError as e: print(f"{path} ভাঙা: লাইন {e.lineno}, কলাম {e.colno} — {e.msg}") return Noneচাবিগুলো নামের string, আর নম্বর list-এ — তাই round trip নিখুঁত: কোনো int চাবি বা tuple নেই যা বদলে যাবে। আরও নিরাপদ করতে চাইলে আগে একটি অস্থায়ী ফাইলে লিখে তারপর নাম বদলান — লেখার মাঝে বিদ্যুৎ গেলে পুরোনো ফাইলটি অক্ষত থাকবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি মার্কশিট CSV পড়ে প্রত্যেকের গড়সহ নতুন CSV লেখার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📂 ফাইল খোলা —
newline=""আর ঠিক encoding দিয়ে - 🧾
csv.DictReaderদিয়ে হেডার পড়ে প্রতিটি সারি dict বানানো - 🔢 প্রতিটি নম্বরের str-কে int-এ বদলানো, ফাঁকা ঘর সামলানো
- ➗ প্রত্যেকের গড় হিসাব করা
- 💾
csv.DictWriterদিয়ে হেডার আর সারি নতুন ফাইলে লেখা
Python-এ json.loads('{"pass": true, "remark": null}')-এর ফল কী?
{'pass': true, 'remark': null}{'pass': True, 'remark': None}{'pass': 'true', 'remark': 'null'}- JSONDecodeError
Python-এ json.loads(json.dumps({1: (2, 3)}))-এর ফল কী?
{1: (2, 3)}{1: [2, 3]}{'1': [2, 3]}- TypeError
Python-এর csv.reader একটি সারির 101,রিয়া,78 পড়লে ঘরগুলোর ধরন কী হয়?
- তিনটিই str
- int, str, int
- float, str, float
- তিনটিই bytes
Python-এ বাংলা লেখা থাকা একটি dict-কে json.dumps দিয়ে লিখলে আউটপুটে বাংলার জায়গায় \u09.. চিহ্ন আসে। বাংলা অক্ষর ঠিক রাখতে কোন argument লাগে?
indent=2encoding="utf-8"sort_keys=Trueensure_ascii=False
Python-এ csv module দিয়ে লেখার জন্য ফাইল খুলতে newline="" দেওয়ার মূল কারণ কী?
- বাংলা অক্ষর ঠিক রাখতে
- লাইন-শেষ csv module নিজে সামলাবে, Python যেন আবার অনুবাদ না করে
- ফাইলের শেষের ফাঁকা লাইন মুছে দিতে
- কমার বদলে সেমিকোলন ব্যবহার করতে
ইন্টারনেট থেকে নামানো অজানা উৎসের একটি ডেটা ফাইল Python-এ পড়ার জন্য নিচের কোনটি সবচেয়ে বিপজ্জনক?
json.load(f)csv.reader(f)pickle.load(f)f.read()
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- সমতল টেবিল — CSV, nested ডেটা — JSON. দুটিই লেখা-ভিত্তিক ফরম্যাট; csv আর json module এগুলো আর list/dict-এর মধ্যে অনুবাদ করে।
- CSV কখনো
split(",")দিয়ে নয় —csv.reader/DictReader. ফাইল খুলুনnewline=""দিয়ে; Excel-এর বাংলা CSV-তেencoding="utf-8-sig"; অন্য delimiter হলেdelimiter=";". - CSV-র প্রতিটি ঘর str — পড়ামাত্র
int()/float()করুন, ফাঁকা ঘরের সিদ্ধান্ত নিন। dumps/loadsstring,dump/loadফাইল। tuple ফেরে list হয়ে, চাবি ফেরে str হয়ে। বাংলার জন্যensure_ascii=False, মানুষের জন্যindent=2, অচেনা ধরনের জন্যdefault=.- ভাঙা JSON-এ
JSONDecodeErrorধরুন; বড় বা প্রশ্ন-ভারী ডেটায় database; অবিশ্বস্ত ডেটায় কখনোpickleবাevalনয় —json.loadsশুধু ডেটা বানায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৭ · লাইব্রেরি ভ্রমণ — datetime, random, collections, osপরবর্তী পর্বstandard library-র সবচেয়ে কাজের module-গুলো, আর sqlite3-এর ঝলক।
- পর্ব ২৫ · ফাইল I/O, with আর pathlibআগের পর্বopen, encoding, newline আর pathlib — এই পর্বের ভিত্তি আবার ঝালিয়ে নিন।
- Python প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — ৮টি মডিউল, কোন পর্ব কখন।
- C প্রোগ্রামিং · ফাইল I/Oতুলনা করুনfopen, text বনাম binary mode — নিচের স্তরে ফাইল কীভাবে কাজ করে।