بايثون بعد الأساسيات 3: قراءة وكتابة ملفات CSV داخل مشروع Python

قراءة وكتابة ملفات CSV داخل مشروع Python منظم بعد تعلم الأساسيات

بعد أن تتعلم أساسيات التعامل مع ملفات CSV في Python، ستلاحظ أن الأمثلة البسيطة غالبًا تضع ملف CSV بجانب ملف الكود مباشرة. هذا جيد للتعلم في البداية، لكنه ليس كافيًا عندما تبدأ في بناء مشروع حقيقي ومنظم.

في هذا الدرس من سلسلة بايثون بعد الأساسيات على موقع بايثون العرب سنتعلم كيف نقرأ ونكتب ملفات CSV داخل مشروع Python منظم، بحيث نضع ملفات الإدخال داخل مجلد data، ونحفظ النتائج داخل مجلد output، ونستخدم pathlib لبناء المسارات بطريقة آمنة وواضحة.

{getToc} $title={محتوى المقال}

الفكرة ببساطة: في المشاريع المنظمة لا تضع كل شيء في نفس المكان. اجعل ملفات الإدخال في data، والنتائج في output، واستخدم pathlib بدل كتابة المسارات بشكل عشوائي. {alertInfo}

لماذا نحتاج إلى تنظيم ملفات CSV داخل المشروع؟

عندما تكون في مرحلة الأساسيات، قد تكتب ملفًا مثل students.csv بجانب main.py، ثم تقرأه مباشرة:

with open("students.csv", "r", encoding="utf-8") as file:
    content = file.read()

هذا يعمل في المشاريع الصغيرة جدًا، لكنه يصبح مزعجًا عندما يكبر المشروع. ستبدأ الملفات بالاختلاط: ملفات كود، ملفات CSV، ملفات نتائج، ملفات تجارب، وربما ملفات مؤقتة.

لذلك نرتب المشروع بشكل أفضل:

  • main.py لتشغيل البرنامج.
  • data لملفات CSV التي نقرأ منها.
  • output للملفات التي ينتجها البرنامج.
  • helpers.py أو مجلد src للوظائف المساعدة لاحقًا.

هيكل المشروع المقترح

سنستخدم في هذا الدرس هيكلًا بسيطًا ومناسبًا للمستوى بعد الأساسيات:

csv_project/
│
├── main.py
│
├── data/
│   └── students.csv
│
└── output/
    └── passed_students.csv

المعنى:

  • ملف students.csv هو ملف الإدخال الذي نقرأ منه بيانات الطلاب.
  • ملف passed_students.csv هو ملف النتيجة الذي سننشئه داخل مجلد output.
  • ملف main.py يحتوي على كود القراءة والمعالجة والكتابة.

شرح هيكل مشروع Python منظم يحتوي على مجلد data وملف CSV وملف main.py

تجهيز ملف CSV للتدريب

أنشئ ملفًا داخل مجلد data باسم:

students.csv

واكتب داخله البيانات التالية:

name,grade,city
Ali,90,Aden
Sara,72,Sanaa
Omar,45,Taiz
Mona,88,Aden
Khaled,50,Hodeidah

هذا الملف يحتوي على ثلاثة أعمدة:

  • name: اسم الطالب.
  • grade: الدرجة.
  • city: المدينة.

سنقرأ هذا الملف، ثم نستخرج الطلاب الناجحين فقط، ونحفظهم في ملف جديد داخل output.

لماذا نستخدم pathlib بدل المسارات النصية؟

يمكنك كتابة المسار هكذا:

"data/students.csv"

وقد يعمل غالبًا. لكن في المشاريع الأفضل استخدام pathlib لأنها تجعل التعامل مع المسارات أوضح وأسهل، وتعمل بشكل أفضل عبر أنظمة مختلفة مثل Windows و Linux و macOS.

مثال:

from pathlib import Path

file_path = Path("data") / "students.csv"

print(file_path)

بدل أن تجمع النصوص يدويًا، نستخدم علامة / مع Path لبناء المسار.

قراءة ملف CSV من مجلد data

الآن سنقرأ ملف CSV من مجلد data باستخدام csv.DictReader. هذا يجعل كل صف يظهر كقاموس، بحيث نستطيع الوصول إلى القيم بأسماء الأعمدة.

import csv
from pathlib import Path

input_file = Path("data") / "students.csv"

with input_file.open("r", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)

    for row in reader:
        print(row)

الناتج سيكون قريبًا من:

{'name': 'Ali', 'grade': '90', 'city': 'Aden'}
{'name': 'Sara', 'grade': '72', 'city': 'Sanaa'}
{'name': 'Omar', 'grade': '45', 'city': 'Taiz'}

لاحظ أن قيمة grade جاءت كنص، لذلك عند المقارنة الرقمية سنحتاج إلى تحويلها باستخدام int().


قراءة ملف CSV من مجلد data داخل مشروع Python باستخدام pathlib و csv reader

استخراج الطلاب الناجحين من ملف CSV

لنعتبر أن الطالب ناجح إذا كانت درجته أكبر من أو تساوي 60. سنقرأ الصفوف، ونضيف الطلاب الناجحين إلى قائمة جديدة.

import csv
from pathlib import Path

input_file = Path("data") / "students.csv"

passed_students = []

with input_file.open("r", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)

    for row in reader:
        grade = int(row["grade"])

        if grade >= 60:
            passed_students.append(row)

print(passed_students)

بهذا أصبحت لدينا قائمة تحتوي فقط على الطلاب الناجحين.

إنشاء مجلد output إذا لم يكن موجودًا

قبل أن نكتب ملف النتائج، يجب أن نتأكد أن مجلد output موجود. إذا لم يكن موجودًا، سننشئه باستخدام mkdir.

from pathlib import Path

output_dir = Path("output")
output_dir.mkdir(exist_ok=True)

معنى exist_ok=True أن Python لن يعطي خطأ إذا كان المجلد موجودًا من قبل.

كتابة النتائج داخل ملف CSV جديد

سنكتب الطلاب الناجحين داخل ملف جديد:

output/passed_students.csv

الكود:

import csv
from pathlib import Path

output_dir = Path("output")
output_dir.mkdir(exist_ok=True)

output_file = output_dir / "passed_students.csv"

fieldnames = ["name", "grade", "city"]

with output_file.open("w", encoding="utf-8", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerows(passed_students)

هنا استخدمنا DictWriter لأن البيانات لدينا على شكل قواميس، وهذا يجعل كتابة CSV أوضح وأسهل.


كتابة نتائج CSV داخل مجلد output في مشروع Python باستخدام csv writer

الكود الكامل للمشروع

ضع الكود التالي داخل ملف main.py:

import csv
from pathlib import Path

input_file = Path("data") / "students.csv"
output_dir = Path("output")
output_file = output_dir / "passed_students.csv"

passed_students = []

with input_file.open("r", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)

    for row in reader:
        grade = int(row["grade"])

        if grade >= 60:
            passed_students.append(row)

output_dir.mkdir(exist_ok=True)

fieldnames = ["name", "grade", "city"]

with output_file.open("w", encoding="utf-8", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerows(passed_students)

print("تم حفظ الطلاب الناجحين في:", output_file)

عند تشغيل البرنامج، سيتم إنشاء ملف داخل مجلد output يحتوي على الطلاب الناجحين فقط.

مشكلة مهمة: تشغيل الملف من مكان مختلف

أحيانًا يكون ملف main.py داخل المشروع، لكنك تشغل البرنامج من مجلد آخر. هنا قد لا يجد Python مجلد data؛ لأن المسار النسبي يعتمد على مكان التشغيل الحالي.

لتقليل هذه المشكلة، يمكن بناء المسارات اعتمادًا على مكان ملف main.py نفسه:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent

input_file = BASE_DIR / "data" / "students.csv"
output_file = BASE_DIR / "output" / "passed_students.csv"

هذا الأسلوب أفضل في المشاريع، لأن البرنامج سيبحث عن data و output بالنسبة لمكان ملف الكود، وليس بالنسبة للمكان الذي شغلت منه الأمر.

نصيحة مهمة: عند بناء مشروع حقيقي، استخدم BASE_DIR = Path(__file__).resolve().parent حتى لا تتعطل المسارات عند تشغيل البرنامج من مكان مختلف. {alertSuccess}

نسخة محسنة من الكود باستخدام BASE_DIR

هذه نسخة أكثر تنظيمًا من الكود الكامل:

import csv
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent

input_file = BASE_DIR / "data" / "students.csv"
output_dir = BASE_DIR / "output"
output_file = output_dir / "passed_students.csv"

passed_students = []

with input_file.open("r", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)

    for row in reader:
        grade = int(row["grade"])

        if grade >= 60:
            passed_students.append(row)

output_dir.mkdir(exist_ok=True)

fieldnames = ["name", "grade", "city"]

with output_file.open("w", encoding="utf-8", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(passed_students)

print("تم حفظ الملف بنجاح:", output_file)

تقسيم الكود إلى دوال

في مرحلة بعد الأساسيات، من الأفضل ألا تكتب كل شيء كسطور متتابعة فقط. يمكنك تقسيم العمل إلى دوال صغيرة:

  • دالة لقراءة الطلاب.
  • دالة لتصفية الطلاب الناجحين.
  • دالة لكتابة النتائج.

مثال:

import csv
from pathlib import Path

def read_students(file_path):
    with file_path.open("r", encoding="utf-8", newline="") as file:
        return list(csv.DictReader(file))


def filter_passed_students(students):
    passed = []

    for student in students:
        if int(student["grade"]) >= 60:
            passed.append(student)

    return passed


def write_students(file_path, students):
    fieldnames = ["name", "grade", "city"]

    with file_path.open("w", encoding="utf-8", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(students)

تقسيم الكود إلى دوال يجعل المشروع أوضح وأسهل في التعديل والاختبار.

كود كامل منظم باستخدام الدوال

هذا مثال كامل أكثر تنظيمًا:

import csv
from pathlib import Path


def read_students(file_path):
    with file_path.open("r", encoding="utf-8", newline="") as file:
        return list(csv.DictReader(file))


def filter_passed_students(students, minimum_grade=60):
    return [
        student
        for student in students
        if int(student["grade"]) >= minimum_grade
    ]


def write_students(file_path, students):
    fieldnames = ["name", "grade", "city"]

    with file_path.open("w", encoding="utf-8", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(students)


def main():
    base_dir = Path(__file__).resolve().parent

    input_file = base_dir / "data" / "students.csv"
    output_dir = base_dir / "output"
    output_file = output_dir / "passed_students.csv"

    output_dir.mkdir(exist_ok=True)

    students = read_students(input_file)
    passed_students = filter_passed_students(students)

    write_students(output_file, passed_students)

    print("تم حفظ الطلاب الناجحين في:", output_file)


if __name__ == "__main__":
    main()

هذا الشكل أقرب لطريقة كتابة المشاريع الحقيقية، لأنه يقسم العمل إلى أجزاء واضحة بدل كود طويل متداخل.

التعامل مع أخطاء الملفات بطريقة أفضل

في المشاريع العملية قد تواجه أخطاء مثل:

  • FileNotFoundError: الملف غير موجود.
  • PermissionError: لا توجد صلاحية للقراءة أو الكتابة.
  • UnicodeDecodeError: مشكلة في ترميز الملف.
  • ValueError: قيمة الدرجة غير قابلة للتحويل إلى رقم.

يمكنك استخدام try و except لعرض رسالة واضحة:

try:
    students = read_students(input_file)

except FileNotFoundError:
    print("ملف الطلاب غير موجود داخل مجلد data.")

except UnicodeDecodeError:
    print("تعذر قراءة الملف بسبب مشكلة في الترميز.")

except PermissionError:
    print("لا توجد صلاحية للوصول إلى الملف.")

هذه الفكرة مهمة لأنها تجعل البرنامج أكثر وضوحًا للمستخدم بدل أن يتوقف برسالة خطأ طويلة.

متى أستخدم csv.reader ومتى أستخدم DictReader؟

عند قراءة CSV لديك أكثر من طريقة. أبسط طريقة هي csv.reader، لكنها تعطيك كل صف كقائمة. أما DictReader فيعطيك كل صف كقاموس يعتمد على أسماء الأعمدة.

الأداة شكل الصف متى تستخدمها؟
csv.reader قائمة مثل ['Ali', '90', 'Aden'] عندما يكون الملف بسيطًا ولا تحتاج أسماء الأعمدة
csv.DictReader قاموس مثل {'name': 'Ali', 'grade': '90'} عندما تريد كودًا أوضح يعتمد على أسماء الأعمدة
csv.writer يكتب صفوفًا كقوائم عند كتابة CSV بسيط
csv.DictWriter يكتب صفوفًا كقواميس عند كتابة بيانات لها أسماء أعمدة واضحة

أخطاء شائعة عند استخدام CSV داخل مشروع Python


أفضل ممارسات التعامل مع ملفات CSV داخل مشاريع Python المنظمة

1. وضع ملفات CSV بجانب ملفات الكود دائمًا

هذا قد يصلح للتجربة، لكن في المشروع الأفضل استخدام مجلد data للمدخلات و output للنتائج.

2. الاعتماد على مسار نسبي بدون فهم مكان التشغيل

إذا كتبت Path("data/students.csv") فقد يتغير السلوك إذا شغلت البرنامج من مكان مختلف. استخدام BASE_DIR يجعل المسار أكثر استقرارًا.

3. نسيان newline عند كتابة CSV

عند كتابة ملفات CSV في Python، خصوصًا على Windows، يفضل استخدام newline="" لتجنب أسطر فارغة إضافية.

4. نسيان encoding

اكتب encoding="utf-8" عند القراءة والكتابة. وإذا كان الملف صادرًا من Excel وفيه مشكلة، جرّب utf-8-sig.

5. عدم تحويل الأرقام من نصوص

كل القيم القادمة من CSV تُقرأ غالبًا كنصوص. إذا أردت المقارنة أو الحساب، حوّل القيمة:

grade = int(row["grade"])

أفضل ممارسات CSV داخل مشروع Python

  • ضع ملفات الإدخال داخل مجلد data.
  • ضع الملفات الناتجة داخل مجلد output.
  • استخدم pathlib لبناء المسارات بدل جمع النصوص يدويًا.
  • استخدم BASE_DIR إذا كان المشروع سيعمل من أماكن مختلفة.
  • استخدم DictReader و DictWriter عندما تكون الأعمدة واضحة.
  • اكتب encoding="utf-8" عند القراءة والكتابة.
  • استخدم newline="" عند التعامل مع CSV.
  • حوّل القيم الرقمية من نصوص إلى أرقام قبل الحساب أو المقارنة.
  • قسّم الكود إلى دوال بدل وضع كل شيء في ملف واحد طويل.
  • تعامل مع الأخطاء المتوقعة باستخدام try و except.

تدريب عملي

أنشئ مشروعًا جديدًا باسم sales_project بهذا الشكل:

sales_project/
│
├── main.py
│
├── data/
│   └── sales.csv
│
└── output/

داخل ملف sales.csv اكتب:

product,price,quantity
Book,5,3
Pen,2,10
Bag,20,1

المطلوب:

  1. اقرأ ملف sales.csv من مجلد data.
  2. احسب إجمالي كل منتج: price * quantity.
  3. اكتب ملفًا جديدًا داخل output باسم sales_report.csv.
  4. اجعل الأعمدة الجديدة: product و price و quantity و total.

حل مختصر للتدريب

import csv
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent

input_file = BASE_DIR / "data" / "sales.csv"
output_dir = BASE_DIR / "output"
output_file = output_dir / "sales_report.csv"

output_dir.mkdir(exist_ok=True)

report_rows = []

with input_file.open("r", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)

    for row in reader:
        price = float(row["price"])
        quantity = int(row["quantity"])
        total = price * quantity

        report_rows.append({
            "product": row["product"],
            "price": price,
            "quantity": quantity,
            "total": total
        })

fieldnames = ["product", "price", "quantity", "total"]

with output_file.open("w", encoding="utf-8", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(report_rows)

print("تم إنشاء تقرير المبيعات:", output_file)

روابط داخلية مفيدة من بايثون العرب

مصادر خارجية مفيدة للتوسع

الخلاصة

قراءة وكتابة ملفات CSV داخل مشروع Python لا تعني فقط استخدام مكتبة csv، بل تعني أيضًا تنظيم الملفات والمسارات بطريقة صحيحة. عندما تضع ملفات الإدخال داخل data، وتحفظ النتائج داخل output، وتستخدم pathlib لبناء المسارات، يصبح مشروعك أوضح وأسهل في التطوير.

في هذا الدرس تعلمنا قراءة ملف CSV من مجلد data، تصفية البيانات، كتابة النتائج داخل output، استخدام BASE_DIR لتثبيت المسارات، وتقسيم الكود إلى دوال. وهذه خطوات مهمة جدًا للانتقال من كتابة أمثلة بسيطة إلى بناء مشاريع Python منظمة.

الخلاصة العملية: لا تجعل ملفات CSV مبعثرة بجانب الكود. أنشئ مجلد data للمدخلات، ومجلد output للمخرجات، واستخدم pathlib و DictReader و DictWriter لكتابة كود أوضح. {alertSuccess}

أسئلة شائعة مع إجاباتها

أين أضع ملفات CSV داخل مشروع Python؟

الأفضل وضع ملفات الإدخال داخل مجلد باسم data، ووضع الملفات الناتجة داخل مجلد باسم output.

لماذا أستخدم pathlib مع ملفات CSV؟

لأن pathlib تجعل بناء المسارات أوضح وأسهل، وتقلل مشاكل اختلاف المسارات بين أنظمة التشغيل.

ما الفرق بين csv.reader و csv.DictReader؟

csv.reader يعيد كل صف كقائمة، أما csv.DictReader فيعيد كل صف كقاموس باستخدام أسماء الأعمدة.

هل يجب استخدام newline="" عند كتابة CSV؟

نعم، يفضل استخدام newline="" مع open() عند قراءة وكتابة CSV لتجنب مشاكل الأسطر الإضافية خصوصًا على Windows.

لماذا تظهر الأرقام من CSV كنصوص؟

لأن CSV ملف نصي، لذلك غالبًا تُقرأ القيم كنصوص. إذا أردت الحساب أو المقارنة، حوّل القيمة باستخدام int() أو float().

ما فائدة BASE_DIR في مشاريع Python؟

يساعدك BASE_DIR على بناء المسارات اعتمادًا على مكان ملف الكود نفسه، وليس المكان الذي شغلت منه البرنامج.

إرسال تعليق

أحدث أقدم