JSON আর CSV
csv মডিউল দিয়ে উদ্ধৃতিসহ টেবিল পড়া ও লেখা, newline="" কেন লাগে, json দিয়ে গঠনযুক্ত তথ্য রাখা, আর কোন ধরনগুলো যাত্রাপথে বদলে যায়।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
তেইশতম অধ্যায় থেকে আমরা ফাইল ভাঙছি .split(",") দিয়ে, আর সেটা কাজও করছিল। এবার একটা ফাইল, যেখানে একটা ক্ষেত্রের ভিতরে কমা আছে:
name,note,price
pen,blue ink,15.0
bag,"large, sturdy",850.0with open("items.csv", "r", encoding="utf-8") as fh:
for line in fh:
print(line.strip().split(","))['name', 'note', 'price']
['pen', 'blue ink', '15.0']
['bag', '"large', ' sturdy"', '850.0']শেষ সারিটায় তিনটে ক্ষেত্রের বদলে চারটে এসেছে, আর উদ্ধৃতি চিহ্নগুলো তথ্যের অংশ হয়ে গেছে।
উদ্ধৃতিটা ভুল নয় — ওটাই CSV-র নিয়ম, ঠিক এই পরিস্থিতির জন্যই আছে। ভুলটা আমাদের পাঠকের। আর নিয়মটা নিজে লিখতে গেলে দেখা যাবে উদ্ধৃতির ভিতরে উদ্ধৃতি, ক্ষেত্রের ভিতরে নতুন লাইন — একটা ছোট কাজ ধীরে ধীরে একটা বড় কাজ হয়ে উঠছে।
এই কাজটা আগেই করা আছে।
import csv
with open("items.csv", "r", encoding="utf-8", newline="") as fh:
for row in csv.reader(fh):
print(row)['name', 'note', 'price']
['pen', 'blue ink', '15.0']
['bag', 'large, sturdy', '850.0']এই অধ্যায়টা দুটো ফরম্যাট নিয়ে — টেবিলের জন্য CSV, আর গঠনযুক্ত তথ্যের জন্য JSON — আর দুটোরই মডিউল পাইথনের সাথেই আসে।
এই অধ্যায় শেষে আপনি পারবেন
csv.readerআরcsv.DictReaderদিয়ে পড়তে, আরcsv.writerদিয়ে লিখতে- বলতে
newline=""কেন লেখা হয় json.dumps/loadsআরjson.dump/load-এর পার্থক্য বলতে- বলতে JSON থেকে কোন পাইথন ধরনগুলো ফিরে আসে আর কোনগুলো আসে না
- কখন CSV আর কখন JSON — সিদ্ধান্ত নিতে
JSONDecodeErrorপড়ে সমস্যাটা খুঁজে বের করতে
পূর্বশর্ত: এক্সেপশন।
CSV পড়া
csv.reader প্রতিটা সারিকে একটা লিস্ট হিসেবে দেয়। কিন্তু অবস্থান মনে রাখতে হয় — row[2] কোনটা ছিল যেন?
csv.DictReader প্রথম সারিটাকে শিরোনাম ধরে নেয় আর প্রতিটা সারিকে ডিকশনারি বানায়:
import csv
with open("items.csv", "r", encoding="utf-8", newline="") as fh:
for row in csv.DictReader(fh):
print(row["name"], "-", row["price"], type(row["price"]))pen - 15.0 <class 'str'>
bag - 850.0 <class 'str'>দুটো জিনিস।
নাম দিয়ে পৌঁছানো যায়, তাই কলামের ক্রম বদলালেও কোড ভাঙে না। প্রায় সবসময় DictReader-ই ভালো পছন্দ।
কিন্তু price এখনো একটা লেখা। csv মডিউল ক্ষেত্র আলাদা করে, রূপান্তর করে না — CSV ফাইলে কোথাও লেখা থাকে না কোনটা সংখ্যা আর কোনটা নয়। float() আর int() আপনাকেই ডাকতে হবে, ঠিক তেইশতম অধ্যায়ের মতো।
CSV লেখা
import csv
rows = [["pen", "blue ink", 15.0], ["bag", "large, sturdy", 850.0]]
with open("out.csv", "w", encoding="utf-8", newline="") as fh:
writer = csv.writer(fh)
writer.writerow(["name", "note", "price"])
writer.writerows(rows)name,note,price
pen,blue ink,15.0
bag,"large, sturdy",850.0লক্ষ্য করুন large, sturdy নিজে থেকেই উদ্ধৃতির ভিতরে চলে গেছে — আর blue ink যায়নি, কারণ তার দরকার ছিল না। যেটা পড়তে আমরা নিজেরা পারিনি, সেটা লিখতেও আমাদের লাগেনি।
ডিকশনারির লিস্ট হলে DictWriter:
import csv
rows = [
{"name": "pen", "price": 15.0},
{"name": "bag", "price": 850.0},
]
with open("out.csv", "w", encoding="utf-8", newline="") as fh:
writer = csv.DictWriter(fh, fieldnames=["name", "price"])
writer.writeheader()
writer.writerows(rows)name,price
pen,15.0
bag,850.0fieldnames কলামের ক্রমও ঠিক করে দেয়, আর writeheader() ছাড়া শিরোনামের সারিটা আসবে না।
newline=""কেন?csvমডিউল নিজেই ঠিক করে সারির শেষে কী বসবে। ফাইলটা স্বাভাবিকভাবে খুললে কিছু সিস্টেমে পাইথন আরেকবার লাইন-শেষ অনুবাদ করে, ফলে প্রতিটা সারির পর একটা করে বাড়তি খালি লাইন পড়ে।newline=""সেই দ্বিতীয় অনুবাদটা বন্ধ করে। পড়া আর লেখা — দুই ক্ষেত্রেই লিখুন।
JSON — গঠন যেখানে টেবিলে ধরে না
CSV একটা টেবিল: সারি আর কলাম, সব সমতল। কিন্তু একটা অর্ডারের ভিতরে যদি লাইনের লিস্ট থাকে, আর প্রতিটা লাইনের ভিতরে ট্যাগের লিস্ট? টেবিলে সেটা ধরে না।
import json
order = {"name": "pen", "price": 15.0, "tags": ["ink", "blue"], "stocked": True, "note": None}
print(json.dumps(order)){"name": "pen", "price": 15.0, "tags": ["ink", "blue"], "stocked": true, "note": null}json.dumps একটা পাইথন বস্তুকে লেখায় রূপান্তর করে। আকারটা পাইথনের মতোই দেখতে, দুটো পার্থক্য ছাড়া: True হয়েছে true, আর None হয়েছে null। JSON আলাদা একটা ভাষা, যা বহু প্রোগ্রামিং ভাষার মধ্যে ভাগ করা — তাই তার নিজের বানান।
মানুষের পড়ার মতো করে চাইলে:
import json
order = {"name": "pen", "tags": ["ink", "blue"]}
print(json.dumps(order, indent=2)){
"name": "pen",
"tags": [
"ink",
"blue"
]
}আর ফিরিয়ে আনা:
import json
text = '{"name": "pen", "price": 15.0, "stocked": true, "note": null}'
order = json.loads(text)
print(order)
print(type(order["price"]), type(order["stocked"]), order["note"] is None){'name': 'pen', 'price': 15.0, 'stocked': True, 'note': None}
<class 'float'> <class 'bool'> Trueএটাই CSV-র সাথে সবচেয়ে বড় পার্থক্য। price ফিরে এসেছে সংখ্যা হয়ে, stocked ফিরে এসেছে True হয়ে — কোনো রূপান্তর ডাকতে হয়নি। JSON ধরনগুলোও বহন করে; CSV করে না।
নাম চারটে মনে রাখা সহজ: dumps/loads লেখা নিয়ে কাজ করে, dump/load ফাইল নিয়ে। শেষের s-টা string।
import json
from pathlib import Path
order = {"name": "pen", "price": 15.0}
Path("order.json").write_text(json.dumps(order, indent=2) + "\n", encoding="utf-8")
with open("order.json", "r", encoding="utf-8") as fh:
back = json.load(fh)
print(back){'name': 'pen', 'price': 15.0}JSON যা ফেরত দেয় না
সব পাইথন বস্তু JSON-এ যায় না, আর যেগুলো যায় তাদের সবাই একই রকম হয়ে ফেরে না।
import json
data = {"pair": (1, 2), "numbers": {3, 4}}
print(json.dumps({"pair": data["pair"]}))
print(json.dumps(data)){"pair": [1, 2]}
TypeError: Object of type set is not JSON serializableটাপল নীরবে লিস্ট হয়ে যায়। JSON-এ টাপল বলে কিছু নেই, তাই লেখার সময় কোনো অভিযোগ আসে না — কিন্তু ফিরিয়ে আনলে আপনি একটা লিস্ট পাবেন। চতুর্দশ অধ্যায়ের সেই «বদলাবে না» প্রতিশ্রুতিটা যাত্রাপথে হারিয়ে যায়।
সেট একেবারেই যায় না, আর সেটা ভালো — নীরবে ভুল হওয়ার চেয়ে থেমে যাওয়া ভালো। দরকার হলে sorted(...) করে লিস্ট বানিয়ে পাঠান।
আরেকটা, যেটা বেশি কামড়ায়:
import json
data = {1: "pen", 2: "bag"}
text = json.dumps(data)
print(text)
print(json.loads(text)){"1": "pen", "2": "bag"}
{'1': 'pen', '2': 'bag'}JSON-এ কী সবসময় লেখা। সংখ্যার কী দেওয়া হলে সে নীরবে লেখা হয়ে যায়, আর ফিরে আসে লেখা হয়েই। তাই data[1] কাজ করত, আর ফেরত আসার পর data["1"] লাগে।
মনে রাখার তালিকাটা ছোট: ডিকশনারি, লিস্ট, লেখা, সংখ্যা, True/False, None — এগুলো নিরাপদ। টাপল বদলে যায়। সেট, তারিখ আর নিজের বানানো বস্তু যায় না।
JSON কড়া
import json
print(json.loads("{'name': 'pen'}"))json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)পাইথনে একক আর দ্বৈত উদ্ধৃতি সমান। JSON-এ নয় — সেখানে কেবল দ্বৈত উদ্ধৃতি চলে, আর শেষ জিনিসটার পর কমা রাখা যায় না।
বার্তাটা লাইন ও কলাম বলে দেয়, তাই বড় ফাইলেও জায়গাটা খুঁজে পাওয়া যায়। আর JSONDecodeError আসলে একটা ValueError — তাই except ValueError দিয়েও ধরা যায়, যেমনটা গত অধ্যায়ের পরিবারের নিয়ম বলে।
কোনটা কখন
CSV নিন যখন তথ্যটা সমতল টেবিল আর মানুষ সেটা স্প্রেডশিটে খুলবে। সারি বেশি হলে ফাইলও ছোট থাকে।
JSON নিন যখন গঠন আছে — ভিতরে লিস্ট, ভিতরে ডিকশনারি — বা যখন ধরনগুলো অক্ষত ফিরে আসা দরকার। সেটিংস ফাইল আর ওয়েব API প্রায় সবসময়ই JSON।
একটা সম্পূর্ণ উদাহরণ
items.csv:
name,note,price,quantity
pen,blue ink,15.0,3
bag,"large, sturdy",850.0,1
ink,,120.0,2
clip,broken,,4main.py:
"""Read a CSV of items, total it, and write the result as JSON."""
import csv
import json
from pathlib import Path
TAX_RATE = 0.15
def read_items(path):
"""Returns the usable rows and the complaints. csv handles the quoting."""
items = []
problems = []
with open(path, "r", encoding="utf-8", newline="") as fh:
for number, row in enumerate(csv.DictReader(fh), start=2):
try:
items.append({
"name": row["name"],
"note": row["note"],
"amount": round(
float(row["price"]) * int(row["quantity"]) * (1 + TAX_RATE), 2),
})
except ValueError as err:
problems.append(f"line {number}: {err}")
return items, problems
def main():
items, problems = read_items(Path("items.csv"))
summary = {
"items": items,
"total": round(sum(item["amount"] for item in items), 2),
"skipped": problems,
}
Path("summary.json").write_text(
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
print(Path("summary.json").read_text(encoding="utf-8"), end="")
if __name__ == "__main__":
main(){
"items": [
{
"name": "pen",
"note": "blue ink",
"amount": 51.75
},
{
"name": "bag",
"note": "large, sturdy",
"amount": 977.5
},
{
"name": "ink",
"note": "",
"amount": 276.0
}
],
"total": 1305.25,
"skipped": [
"line 5: could not convert string to float: ''"
]
}চারটে জিনিস দেখার মতো।
ভিতরে আসে CSV, বাইরে যায় JSON, আর সেটা দুই ফরম্যাটের চরিত্র থেকেই আসে। ইনপুটটা একটা সমতল টেবিল, যা স্প্রেডশিটেও খোলা যায়। আউটপুটটা সমতল নয় — ভিতরে জিনিসের লিস্ট, তার ভিতরে ডিকশনারি, আর পাশে অভিযোগের আরেকটা লিস্ট। এটা CSV-তে লেখা যেত না।
large, sturdy সারাটা পথ অক্ষত। csv সেটাকে ঠিকভাবে পড়েছে, আর json সেটাকে ঠিকভাবে লিখেছে। এই অধ্যায়ের শুরুর সমস্যাটা এখানে কোথাও নেই।
খালি note আর খালি price — দুটোই খালি লেখা, কিন্তু ফল আলাদা। CSV-তে খালি ক্ষেত্র মানে "", None নয়। note লেখা হিসেবেই থেকে যায় আর কোনো সমস্যা করে না; কিন্তু float("") একটা ValueError তোলে, আর তাই clip সারিটা বাদ পড়ে অভিযোগসহ। CSV-তে «নেই» বলে কিছু নেই — কেবল «খালি» আছে, আর তার মানে কী তা আপনাকেই ঠিক করতে হয়।
start=2 দৈব নয়। DictReader প্রথম সারিটা শিরোনাম হিসেবে খেয়ে ফেলে, তাই তার দেওয়া প্রথম তথ্য-সারিটা ফাইলের দ্বিতীয় লাইন। যে বার্তা মানুষ ফাইল খুলে মেলাবে, তার সংখ্যাটা ফাইলের সাথে মিলতে হবে।
কিছু ভাঙা অবস্থা ও তার সমাধান
CSV ফাইলের প্রতিটা সারির পর একটা করে খালি লাইন newline="" দেওয়া হয়নি। লেখার সময় দিন।
KeyError: 'price' — অথচ কলামটা আছে শিরোনামের বানান বা ফাঁকা স্পেস মিলছে না। print(reader.fieldnames) দিয়ে দেখুন পাইথন আসলে কী পড়েছে।
সংখ্যার সাথে হিসাব করতে গিয়ে TypeError csv সব লেখা হিসেবে দেয়। float() বা int() ডাকতে হবে।
প্রথম সারিটাও তথ্য হিসেবে ধরা পড়ছে csv.reader ব্যবহার করছেন, যে শিরোনাম চেনে না। DictReader নিন, নয়তো প্রথম সারিটা আলাদা করে সরান।
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes একক উদ্ধৃতি, নয়তো শেষে বাড়তি কমা। JSON পাইথন নয়।
TypeError: Object of type set is not JSON serializable সেট, তারিখ বা নিজের বানানো কোনো বস্তু পাঠানো হয়েছে। আগে সেটাকে লিস্ট বা লেখায় বদলান।
ফেরত আনার পর আমার টাপলগুলো লিস্ট হয়ে গেছে সেটাই হওয়ার কথা — JSON-এ টাপল নেই। দরকার হলে ফেরত আনার পর নিজে tuple(...) করুন।
data[1] কাজ করছিল, ফেরত আনার পর KeyError JSON-এ কী সবসময় লেখা। এখন data["1"]।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
তিনটে কলামের ফাইল, নিজের হাতে ভাঙা হচ্ছে। কয়টা ক্ষেত্র পাওয়া যাবে?
# items.csv
# name,note,price
# pen,blue ink,15.0
# bag,"large, sturdy",850.0
with open("items.csv", "r", encoding="utf-8") as fh:
for line in fh:
print(len(line.strip().split(",")))- A3 3 4
- B3 3 3
- C3 3 5
- Dএকটি `ValueError`
DictReader সারিটা পড়েছে। দ্বিতীয় লাইনে কী হবে?
# items.csv
# name,price
# pen,15
import csv
with open("items.csv", "r", encoding="utf-8", newline="") as fh:
row = next(csv.DictReader(fh))
print(row)
print(row["price"] + 1)- Aডিকশনারিটা ছাপা হয়, তারপর `TypeError` — `price` এখনো একটা লেখা
- Bডিকশনারিটা ছাপা হয়, তারপর `16`
- Cডিকশনারিটা ছাপা হয়, তারপর `151`
- Dএকটি `KeyError`
json.dumps একটা পাইথন ডিকশনারি পেয়েছে। কী ছাপা হবে?
import json
print(json.dumps({"name": "pen", "stocked": True, "note": None}))- A{"name": "pen", "stocked": true, "note": null}
- B{"name": "pen", "stocked": True, "note": None}
- C{'name': 'pen', 'stocked': True, 'note': None}
- D{"name": "pen", "stocked": true}
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
একটা books.csv বানান — title,author,pages,year কলাম নিয়ে অন্তত ছয়টা সারি। অন্তত একটা শিরোনামে কমা রাখুন (তাই উদ্ধৃতির ভিতরে), একটা সারিতে pages খালি রাখুন, আর একটাতে pages-এর জায়গায় অক্ষর বসান।
তারপর library.py লিখুন যে:
DictReaderদিয়ে ফাইলটা পড়বে, আর খারাপ সারির জন্য লাইন নম্বরসহ অভিযোগ জমাবে- লেখকভিত্তিক একটা সারাংশ বানাবে — কার কটা বই, মোট কত পৃষ্ঠা
- পুরোটা
library.json-এindent=2দিয়ে লিখবে - তারপর ফাইলটা আবার পড়ে নিশ্চিত করবে মোট সংখ্যাটা একই আছে
তারপর পাঁচটা পরীক্ষা:
DictReader-এর বদলেline.split(",")ব্যবহার করুন। কমাওয়ালা শিরোনামটার কী হয়?- লেখার সময়
newline=""সরিয়ে দিন, তারপর CSV ফাইলটা খুলে দেখুন। - সারাংশে একটা সেট রাখুন (যেমন লেখকদের সেট) আর JSON-এ লিখতে চেষ্টা করুন। বার্তাটা পড়ুন, তারপর ঠিক করুন।
library.jsonফাইলটা হাতে খুলে একটা দ্বৈত উদ্ধৃতিকে একক উদ্ধৃতি বানান, তারপর পড়ার চেষ্টা করুন। বার্তাটা লাইন আর কলাম বলে কি?- বছরটাকে ডিকশনারির কী হিসেবে ব্যবহার করুন — যেমন
{2020: [...]}— তারপর লিখে আবার পড়ুন। কী দিয়ে খুঁজতে হচ্ছে এখন?
শেষ দুটো পরীক্ষা এই অধ্যায়ের মূল কথাটা বলে: ফাইলে লেখা মানে অনুবাদ করা, আর প্রতিটা অনুবাদে কিছু না কিছু বদলায়। কী বদলায় তা জানা থাকলে ফরম্যাটটা বেছে নেওয়া যায়; না জানলে ফরম্যাটটাই আপনাকে বেছে নেয়।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন