التعامل مع الملفات
درس 6 من 8
6التعامل مع الملفات (File Handling)
الصورة الكبيرةكيف تفتح ملفًا، تقرأه بثلاث طرق مختلفة، تكتب فيه، وتضمن إغلاقه دائمًا، حتى لو حدث خطأ في منتصف العملية.
ماذا تشاهد في الرسم: with تفتح الملف، والمؤشر ينزل سطرًا بعد سطر أثناء القراءة، وعند نهاية الكتلة يُغلق الملف تلقائيًا.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
فتح الملف مثل استعارة كتاب من المكتبة: تفتحه للقراءة أو للكتابة، وتقرأه كله أو سطرًا سطرًا، وفي النهاية يجب أن تعيده. لو نسيت إعادته يبقى محجوزًا. وجملة with أمين مكتبة يعيد الكتاب عنك تلقائيًا حتى لو تعثّرت في منتصف القراءة.
open(path, mode, encoding="utf-8") بأوضاع r وw وa وb. تقرأ بـread() أو readline() أو بالمرور على الملف سطرًا سطرًا، والمؤشر يتقدّم مع القراءة ويُنقل بـseek. وwith open(...) as f: يضمن close() حتى عند الاستثناء، وحدّد الترميز دائمًا مع النص العربي.
الأساسيات المطلوبة
| المفهوم | لماذا يهمّك هنا |
|---|---|
| معالجة الاستثناءات (درس الاستثناءات) | فتح ملف غير موجود أو بصلاحيات خاطئة يرفع استثناءً (FileNotFoundError, PermissionError) يجب التعامل معه. |
| الحلقات والقوائم | قراءة ملف سطرًا بسطر تعتمد مباشرة على حلقة for. |
المفاهيم الأساسية
أي تعامل مع ملف يمرّ بثلاث خطوات ثابتة: فتح الملف للحصول على مقبض (handle) يشير إليه، تنفيذ عملية قراءة أو كتابة عليه، ثم إغلاقه لتحرير الموارد وضمان حفظ أي بيانات معلّقة في الذاكرة المؤقتة (buffer) إلى القرص فعليًا.
1أوضاع الفتح
"r" قراءة فقط (الافتراضي)، "w" كتابة (يمسح المحتوى القديم بالكامل!)، "a" إضافة في النهاية بدون مسح، "r+" قراءة وكتابة معًا.
2ثلاث طرق للقراءة
.read() يرجع كل المحتوى كنص واحد، .readline() يرجع سطرًا واحدًا فقط في كل استدعاء، .readlines() يرجع قائمة بكل الأسطر.
3موقع المؤشر (Cursor)
الملف المفتوح يتذكر "أين توقفت آخر مرة". .tell() يخبرك بالموقع الحالي بالبايت، .seek(0) يرجعك لبداية الملف. مثلًا: إن قرأت أول 50 بايت بـ.read(50)، فسيصبح .tell() تلقائيًا يساوي 50، وأي قراءة تالية تبدأ من هناك لا من البداية؛ استدعاء .seek(0) يعيدك للبداية بالضبط، دون الحاجة لإغلاق الملف وإعادة فتحه.
4الترميز (Encoding)
تحديد encoding="utf-8" صراحة عند الفتح يحميك من قراءة نصوص عربية أو رموز خاصة بشكل خاطئ على أنظمة تشغيل مختلفة.
الرسم 17: جملة with تضمن استدعاء close() تلقائيًا بمجرد الخروج من الكتلة، بنجاح أو بفشل.
f = open(...) بدون with ثم نسيان f.close()، أو الأخطر: حدوث استثناء بين الفتح والإغلاق، فيبقى الملف مفتوحًا للأبد في تلك الجلسة ولا تُكتب البيانات المعلّقة في الذاكرة المؤقتة إلى القرص فعليًا.
مثال تطبيقي: دفتر يوميات قراءة
لنبنِ برنامجًا صغيرًا يسجّل "يوميات قراءة"، كل مرة تنهي كتابًا تضيف سطرًا جديدًا، وفي أي وقت تستطيع أن تسترجع كل اليوميات أو تتفقدها سطرًا بسطر:
## إضافة إدخال جديد لليوميات دون مسح القديم
def add_entry(path, book_title):
with open(path, "a", encoding="utf-8") as f:
f.write(f"قرأت كتاب: {book_title}\n")
## قراءة كل اليوميات دفعة واحدة
def read_all(path):
with open(path, "r", encoding="utf-8") as f:
return f.read()
## قراءة اليوميات سطرًا بسطر (مفيد للملفات الكبيرة جدًا)
def print_entries_one_by_one(path):
with open(path, "r", encoding="utf-8") as f:
line = f.readline()
while line:
print("📖", line.strip())
line = f.readline()
add_entry("diary.txt", "مئة عام من العزلة")
add_entry("diary.txt", "كائن لا تحمل خفته")
print_entries_one_by_one("diary.txt")
"a" لا "w" هنا؟
لو استخدمنا "w" في add_entry، كل استدعاء جديد سيمسح كل اليوميات السابقة ويبدأ من الصفر! "a" يضيف في النهاية دون المساس بما سبق، هذا الفرق هو مصدر فقدان بيانات شائع جدًا للمبتدئين.
الفرق العملي بين طرق القراءة الثلاث
| الطريقة | ترجع | الأفضل عندما |
|---|---|---|
.read() | نص واحد كامل | الملف صغير وتحتاج كل المحتوى دفعة واحدة |
.readline() | سطر واحد فقط، ويتذكر موقعه للمرة القادمة | تحتاج معالجة سطر بسطر بدون تحميل الملف كله في الذاكرة |
.readlines() | قائمة بكل الأسطر | تحتاج كل الأسطر كقائمة جاهزة للتكرار عليها أكثر من مرة |
مثال محلول
with open("log.txt", "a", encoding="utf-8") as f: f.write("دخل مستخدم\n"). الوضع "a" يضيف في الآخر، بينما "w" كان سيمسح الملف كله.خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
f.close() اليدوي كافٍ دائمًا | لو حدث استثناء قبل الوصول لسطر close()، لن يُنفَّذ أبدًا، with يضمن الإغلاق في كل الحالات بفضل آلية مشابهة لـfinally. |
قراءة ملف كبير بـ.read() دائمًا أسرع | تحميل ملف ضخم (غيغابايتات) بالكامل في الذاكرة دفعة واحدة قد يستهلك كل ذاكرة النظام، القراءة سطرًا بسطر أكثر أمانًا لملفات كبيرة. |
| الترميز الافتراضي يعمل في كل مكان | الترميز الافتراضي يختلف بين أنظمة التشغيل (Windows مقابل Linux)؛ تحديد encoding="utf-8" صراحة يجعل الكود يعمل بنفس الطريقة على أي جهاز. |
أين تُستخدم هذه الفكرة فعليًا؟
ملفات الإعدادات (Config)
قراءة إعدادات التطبيق من ملف نصي عند بدء التشغيل.
سجلات التشغيل (Logs)
إضافة سطر جديد لملف سجل بدون مسح السجل القديم، باستخدام وضع "a".
نقاط الحفظ (Checkpoints)
حفظ تقدّم عملية طويلة على القرص بشكل دوري، للاستئناف بعد انقطاع.
تصدير البيانات
كتابة نتائج تحليل أو تقرير إلى ملف نصي يمكن مشاركته.
جرّب بنفسك
أي وضع فتح يضيف في آخر الملف دون أن يمسح ما فيه؟
"a" للإضافة. "w" يمسح الملف أولًا، و"r" للقراءة فقط، و"x" يفشل إن كان الملف موجودًا.
بطاقات مراجعة سريعة
اختبر فهمك
1. لو فتحت ملفًا بوضع "w" لمجرد قراءته بالخطأ، ماذا يحدث؟
يُمسح محتوى الملف بالكامل فورًا عند الفتح بهذا الوضع، حتى لو لم تكتب أي شيء بعد، فقدان بيانات لا يمكن التراجع عنه. لهذا السبب "r" هو الوضع الآمن الافتراضي للقراءة فقط.
2. ما الذي يضمنه with أكثر من close() اليدوي؟
يضمن تنفيذ الإغلاق حتى عند حدوث استثناء غير متوقع في منتصف الكتلة، تمامًا كما تضمن كتلة finally التنفيذ دائمًا (درس الاستثناءات).
3. لماذا تحتاج .tell() و.seek() أساسًا إذا كان readline() يتذكر موقعه بنفسه؟
readline() يتحرك للأمام فقط تلقائيًا. seek() يتيح لك التحكم اليدوي بالموقع، مثل الرجوع لبداية الملف لقراءته مرة ثانية، أو التنقل لموقع محدد بالبايت دون قراءة كل ما قبله.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي3
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.