JSON আর CSV
csv মডিউল দিয়ে কোটেশনসহ টেবিল পড়া ও লেখা, newline="" কেন লাগে, json দিয়ে গঠনযুক্ত ডেটা রাখা, আর কোন টাইপগুলো যাত্রাপথে বদলে যায়।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
২৩ নম্বর অধ্যায় থেকে আমরা ফাইল ভাঙছি .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মডিউল নিজেই ঠিক করে সারির শেষে কী বসবে (\r\n)। ফাইলটা স্বাভাবিকভাবে খুললে উইন্ডোজে পাইথন আরেকবার লাইন-শেষ অনুবাদ করে, ফলে প্রতিটা সারির পর একটা করে বাড়তি খালি লাইন পড়ে। লিনাক্স আর macOS-এ এই দ্বিতীয় অনুবাদ হয় না, তাই সেখানে পার্থক্যটা চোখে পড়ে না।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(এররের আগে পাইথন traceback-এর কয়েকটি লাইনও ছাপে; এই অধ্যায়ে শুধু শেষ লাইনটি দেখানো হয়েছে।)
টাপল নীরবে লিস্ট হয়ে যায়। 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-এ key সবসময় স্ট্রিং। সংখ্যার key দেওয়া হলে সে নীরবে স্ট্রিং হয়ে যায়, আর ফিরে আসে স্ট্রিং হয়েই। তাই 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-এ key সবসময় স্ট্রিং। এখন 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ডিকশনারিটা ছাপা হয়, তারপর `16`
- Bডিকশনারিটা ছাপা হয়, তারপর `151`
- Cডিকশনারিটা ছাপা হয়, তারপর `TypeError` — `price` এখনো একটা স্ট্রিং
- Dএকটি `KeyError`
json.dumps একটা পাইথন ডিকশনারি পেয়েছে। কী ছাপা হবে?
import json
print(json.dumps({"name": "pen", "stocked": True, "note": None}))- A{'name': 'pen', 'stocked': True, 'note': None}
- B{"name": "pen", "stocked": True, "note": None}
- C{"name": "pen", "stocked": true}
- D{"name": "pen", "stocked": true, "note": null}
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
একটা books.csv বানান — title,author,pages,year কলাম নিয়ে অন্তত ছয়টা সারি। অন্তত একটা title-এ কমা রাখুন (তাই কোটেশনের ভিতরে), একটা সারিতে pages খালি রাখুন, আর একটাতে pages-এর জায়গায় অক্ষর বসান।
তারপর library.py লিখুন যে:
DictReaderদিয়ে ফাইলটা পড়বে, আর খারাপ সারির জন্য লাইন নম্বরসহ অভিযোগ জমাবে- লেখকভিত্তিক একটা সারাংশ বানাবে — কার কটা বই, মোট কত পৃষ্ঠা
- পুরোটা
library.json-এindent=2দিয়ে লিখবে - তারপর ফাইলটা আবার পড়ে নিশ্চিত করবে মোট সংখ্যাটা একই আছে
তারপর পাঁচটা পরীক্ষা:
DictReader-এর বদলেline.split(",")ব্যবহার করুন। কমাওয়ালাtitle-টার কী হয়?- লেখার সময়
newline=""সরিয়ে দিন, তারপর CSV ফাইলটা খুলে দেখুন। উইন্ডোজে প্রতিটা সারির পর একটা খালি লাইন দেখবেন; লিনাক্স আর macOS-এ কোনো পার্থক্য চোখে পড়বে না — তবু সেখানেওnewline=""রাখুন, যাতে কোডটা সব জায়গায় একইভাবে চলে। - সারাংশে একটা সেট রাখুন (যেমন লেখকদের সেট) আর JSON-এ লিখতে চেষ্টা করুন। বার্তাটা পড়ুন, তারপর ঠিক করুন।
library.jsonফাইলটা হাতে খুলে একটা ডাবল কোটেশনকে সিঙ্গেল কোটেশন বানান, তারপর পড়ার চেষ্টা করুন। বার্তাটা লাইন আর কলাম বলে কি?- বছরটাকে ডিকশনারির key হিসেবে ব্যবহার করুন — যেমন
{2020: [...]}— তারপর লিখে আবার পড়ুন। এখন কোন key দিয়ে খুঁজতে হচ্ছে?
শেষ দুটো পরীক্ষা এই অধ্যায়ের মূল কথাটা বলে: ফাইলে লেখা মানে অনুবাদ করা, আর প্রতিটা অনুবাদে কিছু না কিছু বদলায়। কী বদলায় তা জানা থাকলে ফরম্যাটটা বেছে নেওয়া যায়; না জানলে ফরম্যাটটাই আপনাকে বেছে নেয়।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন