JSON وCSV في Python: قراءة وكتابة الملفات بـ json وcsv وpathlib
مكتبة json المدمجة في Python تحوّل القواميس والقوائم إلى نص JSON والعكس؛ وتعمل load وdump مع الملفات، وloads وdumps مع النصوص. ولكتابة نص عربي بشكل مقروء استخدم ensure_ascii=False وافتح الملف بـ encoding="utf-8". أما الجداول فتُقرأ بـ csv.DictReader، والمسارات تُبنى بـ pathlib.
التعامل مع JSON في Python لا يحتاج إلى مكتبات خارجية: الوحدة json موجودة في المكتبة القياسية، وتحوّل القواميس والقوائم إلى نص وتعيدها كما كانت. ويرافقها عادةً ثلاثة أشياء: الدالة open مع التركيب with، والوحدة pathlib للمسارات، والوحدة csv للجداول. وفيما يلي كل شيء بالترتيب، مع كود يعمل والمطبات التي يتعثر فيها الناس عادةً.
الدالة open في Python: الأوضاع والترميز
أي ملف نصي يُفتح بالدالة المدمجة open(path, mode, encoding=...). تعيد كائن ملف فيه الطرائق read وreadline وwrite، ويمكن المرور عليه في حلقة سطراً بسطر.
| الوضع | ماذا يفعل |
|---|---|
"r" | قراءة (الافتراضي). إن لم يوجد الملف يُرفع FileNotFoundError |
"w" | كتابة. يُنشأ الملف أو يُفرَّغ فور فتحه |
"a" | الإضافة في النهاية، دون المساس بالمحتوى الموجود |
"x" | إنشاء ملف جديد؛ إذا كان موجوداً يُرفع FileExistsError |
"b" | إضافة إلى الوضع: العمل بالبايتات ("rb"، "wb") |
لماذا نستخدم with
يجب إغلاق الملف، وإلا فقد لا تُكتب البيانات على القرص، ويبقى الملف مفتوحاً في النظام. والتركيب with يغلق الملف تلقائياً، حتى لو وقع خطأ داخل الكتلة.
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("السطر الأول\n")
f.write("السطر الثاني\n")
with open("notes.txt", encoding="utf-8") as f:
for line in f:
print(line.rstrip("\n"))
المرور على الملف في حلقة يقرأ سطراً سطراً ولا يحمّله كله في الذاكرة، لذلك يمكن معالجة سجلات بحجم جيجابايت بهذه الطريقة. الطريقة f.read() تعيد المحتوى كله كنص واحد، وf.readlines() تعيد قائمة من الأسطر.
كيف تضيف سطراً إلى الملف دون أن تمحو القديم
أكثر الأخطاء شيوعاً عند المبتدئين أن يفتح الملف بالوضع "w" ليضيف سطراً. فالوضع "w" يفرّغ الملف لحظة فتحه، قبل أي write. لإضافة سجل في النهاية استخدم "a"، ولضمان عدم الكتابة فوق ملف موجود استخدم "x".
with open("log.txt", "a", encoding="utf-8") as f:
f.write("سجل جديد\n")
انتبه إلى أن write لا تضيف سطر جديد تلقائياً، فالرمز "\n" يُكتب صراحةً. ويمكن أيضاً استخدام print(..., file=f) الذي يضيف السطر الجديد بنفسه.
ترميز UTF-8: لماذا تظهر الرموز الغريبة
قبل Python 3.15 كانت الدالة open() بدون المعامل encoding تأخذ ترميز النظام. وهو UTF-8 في Linux وmacOS، أما في Windows بلغة عربية فهو cp1256 أو cp1251 حسب الإعداد. فالسكريبت الذي يعمل بسلاسة على الخادم قد يعرض على حاسوب زميل نصوصاً مشوهة بدلاً من الكلمات العربية، أو يسقط بـ UnicodeDecodeError.
في Python 3.15 وفق PEP 686 صار وضع UTF-8 مفعّلاً افتراضياً: تقرأ open() بدون encoding وتكتب بـ UTF-8 على كل الأنظمة. ويمكن الرجوع إلى السلوك القديم بمتغير البيئة PYTHONUTF8=0 أو بالعلم -X utf8=0، ويُطلب ترميز النظام صراحةً بالقيمة encoding="locale".
import sys
print(sys.flags.utf8_mode) # 1 في Python 3.15
open("legacy.txt", encoding="locale") # ترميز النظام
open("old_excel.csv", encoding="cp1256") # ملف محفوظ بترميز cp1256
الوجه الآخر: ملف CSV قديم من Excel أو من سكريبت على Windows محفوظ بترميز cp1256 سيُقرأ الآن كـ UTF-8 فيسبب UnicodeDecodeError. والحل أن تقرأه بالترميز الصحيح ثم تعيد حفظه بـ UTF-8. والقاعدة الجيدة لم تتغير: حدد encoding صراحةً، فيتصرف الكود بالطريقة نفسها في أي إصدار.
pathlib في Python: المسارات ككائنات
دمج المسارات كنصوص ("data" + "/" + name) مزعج ويتعطل على أنظمة مختلفة. تقدم الوحدة pathlib الصنف Path، حيث تُربط المسارات بالعامل /، ولكائن المسار خصائص واضحة.
from pathlib import Path
data = Path("data")
csv_path = data / "expenses.csv"
print(csv_path.name) # expenses.csv
print(csv_path.stem) # expenses
print(csv_path.suffix) # .csv
print(csv_path.parent) # data
print(csv_path.exists())
data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("مرحباً", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))
for p in data.glob("*.csv"):
print(p)
تحل الطريقتان read_text وwrite_text محل التركيب open + with للملفات الصغيرة. ويمكن تمرير كائن Path إلى open العادية وإلى json وإلى csv، فكلها تقبله. ولبناء مسار نسبي إلى السكريبت نفسه لا إلى المجلد الحالي استخدم: Path(__file__).parent / "data".
منذ Python 3.14 صارت للصنف Path الطريقتان copy() وmove()، فلم تعد الوحدة shutil لازمة للنسخ البسيط. وفي الإصدار نفسه أُضيفت إلى المكتبة القياسية الوحدة compression.zstd للضغط بصيغة Zstandard (PEP 784).
JSON في Python: load وdump وloads وdumps
JSON صيغة نصية لتبادل البيانات: تعيدها واجهات API، وتُحفظ فيها الإعدادات والتقارير. تؤدي الوحدة json عمليتين: التسلسل (كائن Python ← نص JSON) وفك التسلسل (نص ← كائن). والدوال أربع، والخلط بينها سؤال متكرر.
| الدالة | ماذا تفعل |
|---|---|
json.dumps(obj) | من كائن إلى نص JSON |
json.loads(s) | من نص JSON إلى كائن |
json.dump(obj, f) | كتابة الكائن في ملف مفتوح |
json.load(f) | قراءة JSON من ملف مفتوح |
للحفظ في الذهن: الحرف s في آخر الاسم يعني string، أي التعامل مع نص؛ وبدونه يكون التعامل مع ملف.
تطابق الأنواع
| Python | JSON |
|---|---|
dict | كائن {} |
list، tuple | مصفوفة [] |
str | نص |
int، float | رقم |
True / False | true / false |
None | null |
الصف يصبح قائمة عند القراءة العكسية، أما المجموعات والتواريخ وDecimal فلا تعرفها الوحدة، وتعطي TypeError: Object of type ... is not JSON serializable.
كيف تحفظ JSON في ملف
import json
from pathlib import Path
report = {
"month": "2026-10",
"total": 15230.5,
"categories": {"طعام": 8200, "مواصلات": 2100},
"tags": ["منزل", "عمل"],
}
path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
المعامل ensure_ascii=False يحفظ الحروف العربية كما هي. وبدونه ستظهر «طعام» في الملف هكذا "طعام"، وهذا JSON صحيح لكن الإنسان لا يقرؤه. والمعامل indent=2 يضيف مسافات بادئة للتنسيق.
كيف تقرأ JSON من ملف
with open("data/report.json", encoding="utf-8") as f:
data = json.load(f)
print(data["total"]) # 15230.5
print(data["categories"]["طعام"]) # 8200
النتيجة قاموس أو قائمة عادية تتعامل معها بالأدوات المعتادة. وإذا كان الملف ليس JSON أو كان مبتوراً فسيُرفع الاستثناء json.JSONDecodeError مع رقم السطر والموضع، ويجب التقاطه عندما تأتي البيانات من خارج البرنامج.
تحليل نص JSON والعرض المنسق
import json
raw = '{"name": "أمينة", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0]) # python
print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))
المعامل sort_keys=True يرتب المفاتيح عند العرض، وهذا مفيد لمقارنة الملفات في git. ولتنسيق ملف من الطرفية توجد أداة مدمجة: python -m json.tool data/report.json.
رد HTTP من API هو أيضاً نص JSON. ومكتبة requests تحلله بالطريقة response.json()، وهي داخلياً json.loads نفسها.
مطب: مفاتيح JSON دائماً نصوص
d = {1: "يناير", 2: "فبراير"}
back = json.loads(json.dumps(d))
print(back == d) # False
print(back) # {'1': 'يناير', '2': 'فبراير'}
# back[1] -> KeyError، والصحيح back["1"]
fixed = {int(k): v for k, v in back.items()}
مفاتيح كائن JSON لا يمكن أن تكون إلا نصوصاً، لذلك تتحول المفاتيح الرقمية إلى نصوص بعد دورة التسلسل. أعدها إلى نوعها الأصلي عند القراءة.
أنواع خاصة: التواريخ وDecimal
import json
from datetime import date
def default(obj):
if isinstance(obj, date):
return obj.isoformat()
raise TypeError(f"لا أستطيع تسلسل {type(obj).__name__}")
print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}
تُستدعى الدالة الممررة في المعامل default مع كل كائن لا تعرفه الوحدة. وفي الحالات المعقدة يمكن الوراثة من json.JSONEncoder وإعادة تعريف الطريقة default.
كيف تضيف عنصراً إلى مصفوفة JSON
لا يمكن إضافة عنصر إلى نهاية JSON كما نفعل مع سجل نصي: يجب قراءة الملف وتعديله ثم كتابته كاملاً.
path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "جديد"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
إذا كانت السجلات كثيرة وتُضاف باستمرار فالأنسب صيغة JSON Lines: كائن JSON واحد في كل سطر، ويُفتح الملف بالوضع "a".
CSV في Python: الوحدة csv
CSV جدول في ملف نصي: الأسطر سجلات، والقيم مفصولة بفواصل. تعالج الوحدة csv علامات الاقتباس والفواصل داخل القيم وأسطر الانتقال بشكل صحيح، وهذا ما لا يفعله التقسيم اليدوي split(",").
import csv
with open("data/expenses.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
total = 0.0
for row in reader:
total += float(row["amount"])
print(row["date"], row["category"], row["amount"])
print("الإجمالي:", total)
تحوّل csv.DictReader كل سطر إلى قاموس حسب رأس الملف. وكل القيم نصوص، فحوّل الأرقام والتواريخ بنفسك (float وint وdate.fromisoformat). والملف الذي يفصل بالفاصلة المنقوطة «;» (كما يحفظه Excel بالعربية غالباً) يُقرأ بالمعامل delimiter=";".
rows = [
{"date": "2026-10-01", "category": "طعام", "amount": 450},
{"date": "2026-10-02", "category": "مواصلات", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
writer.writeheader()
writer.writerows(rows)
عند الكتابة يجب newline=""، وإلا ستظهر أسطر فارغة بين السجلات على Windows. وإذا كان الملف سيُفتح في Excel فاستخدم encoding="utf-8-sig"، فعلامة BOM تساعد Excel على التعرف على UTF-8.
من CSV إلى JSON: مثال كامل
import csv
import json
from collections import defaultdict
from pathlib import Path
src = Path("data/expenses.csv")
dst = Path("data/report.json")
by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
by_category[row["category"]] += float(row["amount"])
report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))
هذا هو جوهر أي سكريبت تقارير: دخول من CSV، وتجميع في قاموس، وخروج إلى JSON.
أخطاء شائعة
FileNotFoundError— المسار نسبي ويُحسب من مجلد التشغيل الحالي لا من السكريبت. استخدمPath(__file__).parent.- الملف تفرّغ — فُتح بالوضع
"w"بدلاً من"a". UnicodeDecodeError— الملف بترميز آخر؛ حدد الترميز صراحةً (cp1256أوutf-8-sig).TypeError: Object of type set is not JSON serializable— حوّل المجموعة إلى قائمة أو مررdefault.KeyErrorبعد تحميل JSON — المفاتيح الرقمية صارت نصوصاً.- سمّيت ملفك
json.pyأوcsv.py— يحجب الوحدة القياسية، فيستوردimport jsonملفك.
الخلاصة
لـ JSON في Python تكفي المكتبة القياسية: json.load وjson.dump للملفات، وloads وdumps للنصوص، وensure_ascii=False وindent للعرض المقروء. افتح الملفات بـ with وبترميز صريح، وابنِ المسارات بـ pathlib، واقرأ الجداول بـ csv.DictReader. وقد شُرح هذا عملياً في الدورة: يقرأ متتبّع المصروفات الإنفاق من CSV ويحفظ التقرير في JSON.
الأسئلة الشائعة
ما الفرق بين json.load وjson.loads؟
تقرأ json.load من كائن ملف مفتوح، أما json.loads فتحلل نصاً. وبالمثل تكتب json.dump في ملف، وتعيد json.dumps نصاً.
كيف أحفظ الحروف العربية في JSON دون رموز \u؟
مرّر ensure_ascii=False إلى json.dump أو json.dumps، وافتح الملف بـ encoding="utf-8".
هل أحتاج إلى تثبيت مكتبة للتعامل مع JSON في Python؟
لا، وحدة json مدمجة في المكتبة القياسية، ويكفي كتابة import json.
هل يمكن حفظ تاريخ أو مجموعة في JSON؟
ليس مباشرة، فيجب تحويلهما: التاريخ إلى نص بصيغة ISO بالدالة isoformat، والمجموعة إلى قائمة، أو تمرير دالة إلى المعامل default.
كيف أقرأ ملف JSON كبيراً لا يتسع في الذاكرة؟
تحمّل json.load الملف كله. وللبيانات الكبيرة احفظها بصيغة JSON Lines واقرأها سطراً سطراً بـ json.loads.
يُشرح هذا الموضوع بالتفصيل مع التطبيق العملي في دورة «Python 3.15: от новичка до профи».
الانتقال إلى الدورة