فصل ۶: فایل‌ها و خطاها — pathlib، UTF-8، CSV، JSON و logging

CSV و JSON: utf-8-sig برای اکسل فارسی و ensure_ascii=False

دو قالب داده‌ای که هر روز با آن‌ها سروکار دارید

CSV زبان مشترک اکسل، نرم‌افزارهای حسابداری و خروجی سامانه‌هاست. JSON زبان مشترک وب، API ها و فایل‌های تنظیمات. پایتون برای هر دو ماژول استاندارد دارد و هر دو یک دام فارسی مهم دارند.

نوشتن CSV که اکسل فارسی را درست نشان دهد

اگر یک CSV با UTF-8 معمولی بسازید و در اکسل ویندوز دوبار کلیک کنید، به‌جای «مریم کاشانی» حروف درهم (دم...) می‌بینید. دلیل: اکسل بدون نشانه‌ی خاص، فایل را با کدگذاری پیش‌فرض ویندوز می‌خواند. آن نشانه، BOM است؛ سه بایت در ابتدای فایل که کدگذاری utf-8-sig خودکار می‌گذارد:

import csv
from pathlib import Path

rows = [
    {"code": "KSH-101", "customer": "مریم کاشانی", "area": 6, "price": 57_000_000},
    {"code": "TBZ-220", "customer": "رضا احمدی", "area": 12, "price": 98_000_000},
]

path = Path("orders.csv")
with open(path, "w", encoding="utf-8-sig", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["code", "customer", "area", "price"])
    writer.writeheader()
    writer.writerows(rows)

و newline="": ماژول csv خودش پایان خط را مدیریت می‌کند؛ بدون این آرگومان، در ویندوز بین هر دو سطر یک سطر خالی ظاهر می‌شود.

خواندن CSV

with open("orders.csv", encoding="utf-8-sig", newline="") as f:
    for row in csv.DictReader(f):
        price = int(row["price"])          # همه‌چیز رشته است!
        print(row["customer"], f"{price // 10:,} تومان")

خواندن با utf-8-sig هم امن است: اگر BOM باشد حذفش می‌کند و اگر نباشد، مثل utf-8 معمولی رفتار می‌کند. بدون آن، اولین کلید دیکشنری به‌جای "code" چیزی مثل "\ufeffcode" می‌شود و row["code"] خطای KeyError می‌دهد؛ باگی که ساعت‌ها وقت می‌گیرد.

JSON

import json

text = json.dumps(rows, ensure_ascii=False, indent=2)
Path("orders.json").write_text(text, encoding="utf-8")

loaded = json.loads(Path("orders.json").read_text(encoding="utf-8"))
print(loaded[0]["customer"])      # مریم کاشانی

with open("orders.json", "w", encoding="utf-8") as f:
    json.dump(rows, f, ensure_ascii=False, indent=2)   # مستقیم در فایل

به‌طور پیش‌فرض json هر کاراکتر غیرانگلیسی را به شکل \u0645\u0631... می‌نویسد؛ فایل درست است ولی برای انسان خواندنی نیست و حجمش چند برابر می‌شود. ensure_ascii=False فارسی را همان‌طور که هست ذخیره می‌کند.

پایتونJSONنکته
dictobjectکلیدها همیشه رشته می‌شوند
list، tuplearraytuple پس از بارگذاری list می‌شود
None / Truenull / true—
Decimal، datetime، setپشتیبانی نمی‌شودTypeError؛ با default=str حل می‌شود

نکته‌هایی که کمتر کسی می‌داند

  • کلید عددی بعد از رفت‌وبرگشت JSON رشته می‌شود: {1: "a"} پس از dumps و loads می‌شود {'1': 'a'} و d[1] دیگر کار نمی‌کند.
  • CSV با جداکننده‌ی نقطه‌ویرگول (رایج در خروجی اکسل با تنظیمات اروپایی) را با csv.DictReader(f, delimiter=";") بخوانید؛ csv.Sniffer هم می‌تواند جداکننده را حدس بزند.
  • در قالب CSV اکسل، کد سفارشی مثل 0087 صفرهای ابتدایی‌اش را از دست می‌دهد؛ این کار اکسل است، نه پایتون. فایل را با Data ← From Text/CSV باز کنید و نوع ستون را Text بگذارید.
  • json.dumps(data, ensure_ascii=False, sort_keys=True) خروجی ثابت و قابل مقایسه در git تولید می‌کند.
  • برای فایل‌های واقعی xlsx (نه CSV) کتابخانه‌ی openpyxl را نصب کنید؛ CSV فقط متن است و فرمول، رنگ و چند شیت ندارد.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.