الملفات ما هي إلا نصوص مربوطة بمسار
في مرحلة ما، أي برنامج تكتبه هيحتاج يقرأ أو يكتب ملف — سواء كان إعدادات، بيانات مستخدمين، سجلات (logs)، تصدير CSV، أو كاش صغير. ولحسن الحظ، التعامل مع الملفات في بايثون سهل جدًا عن طريق دالة مدمجة واحدة هي open()، مع كتلة with اللي تضمن لك إغلاق الملف بشكل سليم.
خلينا نمشي على الحالات الشائعة واحدة واحدة.
قراءة ملف كامل في بايثون
أبسط طريقة لقراءة ملف:
with open("notes.txt") as f:
contents = f.read()
print(contents)
open("notes.txt") يفتح الملف للقراءة (وهذا هو الوضع الافتراضي). أما جملة with فتضمن لك أن بايثون ستغلق الملف تلقائيًا بمجرد الخروج من الكتلة — حتى لو حدث استثناء في المنتصف. وبعد الإغلاق، لن تتمكن من استخدام f مرة أخرى، لكن محتوى النص يبقى محفوظًا في المتغير contents.
المتغير contents عبارة عن سلسلة نصية واحدة تحوي الملف بأكمله. هذا مناسب للملفات الصغيرة، لكنه كارثي مع ملف log حجمه 2 جيجابايت.
قراءة الملف سطرًا بسطر في بايثون
عند التعامل مع الملفات الكبيرة، الأفضل أن تمر على الملف سطرًا سطرًا بدلًا من تحميله كاملًا في الذاكرة:
with open("big.log") as f:
for line in f:
if "ERROR" in line:
print(line.strip())
كل دورة في الحلقة ترجع سطرًا واحدًا مع رمز السطر الجديد في نهايته، ولهذا السبب تجد .strip() يتكرر كثيرًا. ولو أردت قائمة بكل الأسطر، فالدالة f.readlines() تؤدي الغرض، لكن في الغالب يكون التكرار المباشر هو الأنسب.
الكتابة في ملف بايثون
للكتابة، مرّر وضع الفتح كمعامل ثانٍ:
with open("output.txt", "w") as f:
f.write("first line\n")
f.write("second line\n")
نقطتان مهمتان:
"w"يمسح المحتوى القديم. إذا كان الملفoutput.txtموجودًا مسبقًا، فإن محتواه يُمحى بمجرد استدعاءopen.- يجب إضافة أسطر جديدة يدويًا. تستدعي
f.write("hello")فتُكتب هذه الأحرف الخمسة فقط — بدون أي سطر جديد في النهاية.
وللإضافة إلى نهاية الملف بدل الكتابة فوقه:
with open("output.txt", "a") as f:
f.write("another line\n")
يمكنك أيضًا تمرير عدة أسطر دفعة واحدة باستخدام writelines:
lines = ["a\n", "b\n", "c\n"]
with open("letters.txt", "w") as f:
f.writelines(lines)
الطباعة مباشرة في ملف باستخدام print
الدالة print تقبل وسيطاً اسمه file، وهذه طريقة مريحة للكتابة في ملف دون الحاجة لإضافة أسطر جديدة يدوياً:
with open("log.txt", "w") as f:
print("started", file=f)
print("finished", file=f)
كل استدعاء لـ print يطبع الوسائط ويُلحق بها سطرًا جديدًا في النهاية. للطباعة السريعة والبسيطة، عادةً ما تكون هذه أفضل طريقة.
الوضع النصي مقابل الوضع الثنائي
افتراضيًا، تعمل الدالة open في الوضع النصي، حيث تقوم بايثون بفك ترميز البايتات إلى نصوص (باستخدام ترميز UTF-8 افتراضيًا على الأنظمة الحديثة) وتتكفل بتحويل أسطر النهاية تلقائيًا.
أما إذا كنت تتعامل مع الصور أو ملفات PDF أو الملفات المُترجَمة أو أي شيء ليس نصًا، فافتح الملف في الوضع الثنائي بإضافة "b" إلى وضع الفتح:
with open("image.png", "rb") as f:
data = f.read()
print(len(data)) # number of bytes
with open("image_copy.png", "wb") as f:
f.write(data)
في الوضع الثنائي، تحصل على كائنات من نوع bytes بدلاً من str، ولا تتدخل بايثون في أحرف نهاية الأسطر.
تحديد ترميز الملفات في بايثون encoding
عند التعامل مع الملفات النصية، كن صريحاً في تحديد الترميز. في عام 2026، يكون UTF-8 هو الخيار الصحيح في معظم الحالات:
with open("notes.txt", encoding="utf-8") as f:
contents = f.read()
إذا كنت تتعامل مع ملفات قديمة قادمة من بيئة تعتمد على Windows فقط، فربما تصادف ترميزات مثل cp1252 أو latin-1. واختيار الترميز الخاطئ يعني إما أحرفاً مشوّهة أو استثناءً من نوع UnicodeDecodeError.
مكتبة pathlib في بايثون: الطريقة العصرية للتعامل مع المسارات
توفّر لك وحدة pathlib ضمن المكتبة القياسية واجهة أنيقة وعملية أكثر بكثير من التعامل مع المسارات كنصوص خام:
from pathlib import Path
path = Path("notes.txt")
# Simple read and write.
contents = path.read_text(encoding="utf-8")
path.write_text("new contents\n", encoding="utf-8")
# Build paths without worrying about / vs \.
data_dir = Path("data")
log_path = data_dir / "today.log"
print(log_path)
print(log_path.exists())
print(log_path.suffix) # ".log"
print(log_path.stem) # "today"
print(log_path.parent) # "data"
الدالتان Path.read_text() وwrite_text() اختصار مفيد لنمط with open(...) as f في الحالات التي تريد فيها قراءة الملف أو كتابته دفعة واحدة. أما إذا كنت تحتاج إلى المرور على الملف سطرًا بسطر، فستبقى تستخدم open أو path.open().
مثال عملي متكامل
لنقرأ قائمة عناصر من ملف، ونرشّحها، ثم نكتب النتيجة في ملف آخر:
from pathlib import Path
source = Path("items.txt")
destination = Path("filtered.txt")
items = source.read_text().splitlines()
kept = [item for item in items if item and not item.startswith("#")]
destination.write_text("\n".join(kept) + "\n")
print(f"Kept {len(kept)} items out of {len(items)}")
splitlines() هي الرفيقة السطرية للدالة join. تقوم بتقسيم النص عند فواصل الأسطر دون الاحتفاظ بها — وهذا مفيد حين تنوي إعادة دمج الأجزاء لاحقًا على أي حال.
عندما تسوء الأمور
محاولة فتح ملف غير موجود تُطلق الاستثناء FileNotFoundError، وقراءة ملف لا تملك صلاحية الوصول إليه تُطلق PermissionError. كلاهما فئتان فرعيتان من OSError، الذي بدوره فئة فرعية من Exception — وسنتحدث عن التعامل الصحيح مع الاستثناءات في الصفحة التالية.
لكن لنأخذ الآن لمحة سريعة عن شكل معالجة الأخطاء:
from pathlib import Path
path = Path("maybe.txt")
try:
contents = path.read_text()
except FileNotFoundError:
print("File doesn't exist — starting fresh.")
contents = ""
print(repr(contents))
عادات مهمة لازم تمشي عليها
- استخدم دائمًا
with open(...)بدل ما تستعملopenلحاله وتقفل الملف يدويًا. - مرّر دائمًا
encoding=مع الملفات النصية، وخلّي UTF-8 هو الافتراضي عندك. - لما يكون الملف كبير، اقرأه سطرًا سطرًا ولا تستعمل
.read()لتحميله كله دفعة واحدة. - للتعامل مع المسارات وعمليات القراءة والكتابة الشائعة، اعتمد على
pathlib— هيريحك من لفّة كبيرة مع السلاسل النصية.
في الدرس القادم: JSON. معظم الملفات النصية اللي هتتعامل معها في الواقع مش مجرد أسطر عادية، دي بيانات منظّمة، و JSON هو أشهر صيغة لها. ونفس عادات with open(...) اللي اتعلمناها هنا هتنقل معاك بالظبط.
الأسئلة الشائعة
كيف أقرأ ملفًا في بايثون؟
استخدم الدالة open() مع عبارة with، لأنها تُغلق الملف تلقائيًا بمجرد الانتهاء منه. مثلًا: with open('file.txt') as f: contents = f.read() تقرأ محتوى الملف كاملًا في نص واحد. أمّا إذا أردت القراءة سطرًا سطرًا، فكل ما عليك هو المرور على كائن الملف مباشرة: for line in f:.
ما الفرق بين الأوضاع 'r' و 'w' و 'a' في الدالة open؟
'r' للقراءة وهو الوضع الافتراضي. 'w' للكتابة، وينشئ الملف إذا لم يكن موجودًا أو يمسح محتواه بالكامل إذا كان موجودًا. 'a' للإضافة (append)، حيث يكتب في نهاية الملف دون حذف ما بداخله. أضِف 'b' للوضع الثنائي، أو '+' للقراءة والكتابة معًا.
لماذا يُفضَّل استخدام with open بدلًا من open المجرّدة؟
with open بدلًا من open المجرّدة؟عبارة with تضمن إغلاق الملف حتى لو حدث خطأ في منتصف التنفيذ. أما بدونها فأنت مضطر لاستدعاء .close() يدويًا، ويجب أن تتذكّر تنفيذها حتى داخل مسارات الأخطاء. لذلك with أكثر أمانًا وتوفّر عليك أسطرًا من الكود.