أثناء التعامل مع الملفات في بايثون، قد تكتب كودًا يبدو صحيحًا ثم يتوقف البرنامج برسالة مثل:
IsADirectoryError: [Errno 21] Is a directory: 'reports'
هذه الرسالة لا تعني أن مكتبة pathlib فيها مشكلة، ولا أن ترميز الملف خطأ. معناها المباشر أن الكود طلب تنفيذ عملية خاصة بملف، مثل القراءة أو الكتابة، لكنه أعطى بايثون مسار مجلد بدل مسار ملف.
{alertInfo} الفكرة ببساطة: استخدمis_file()عندما تريد قراءة ملف، واستخدمis_dir()عندما تريد التأكد أن المسار يشير إلى مجلد. وجود المسار وحده لا يكفي، لأن المسار قد يكون ملفًا أو مجلدًا.
{getToc} $title={محتوى المقال}
متى يظهر خطأ IsADirectoryError؟
يظهر الخطأ عندما تحاول استخدام مجلد كما لو كان ملفًا. أمثلة العمليات التي تتوقع ملفًا:
- قراءة النص عبر
open()أوread_text(). - الكتابة داخل ملف عبر
write_text()أوopen(..., "w"). - قراءة محتوى ملف CSV أو JSON من مسار يشير في الحقيقة إلى مجلد.
المثال التالي يعيد إنتاج المشكلة:
from pathlib import Path
path = Path("reports")
with path.open("r", encoding="utf-8") as file:
content = file.read()
print(content)
إذا كان reports مجلدًا موجودًا، فإن path.open() لن يستطيع قراءته كنص، لأن هذه العملية تحتاج اسم ملف مثل reports/summary.txt.
في هذا التنظيم:
# خطأ: reports مجلد
Path("reports").read_text(encoding="utf-8")
# صحيح: summary.txt ملف داخل المجلد
file_path = Path("reports") / "summary.txt"
الخطأ الأكثر شيوعًا: الفحص بـ exists فقط
قد يحاول بعض المبتدئين حل المشكلة بهذا الشكل:
from pathlib import Path
path = Path("reports")
if path.exists():
content = path.read_text(encoding="utf-8")
المشكلة أن exists() يجيب عن سؤال واحد فقط: هل يوجد هذا المسار؟ لكنه لا يخبرك هل هو ملف أم مجلد. لذلك قد تكون النتيجة True للمجلد reports، ثم يظهر الخطأ عند محاولة القراءة.
الحل هو فحص نوع المسار قبل محاولة قراءة محتواه:
from pathlib import Path
path = Path("reports")
if path.is_file():
content = path.read_text(encoding="utf-8")
print(content)
elif path.is_dir():
print("المسار يشير إلى مجلد، وليس ملفًا.")
else:
print("المسار غير موجود.")
الفرق بين exists وis_file وis_dir
| الدالة | السؤال الذي تجيب عنه | مثال لنتيجة محتملة |
|---|---|---|
exists() |
هل يوجد المسار أصلًا؟ | قد تكون True للملف أو المجلد. |
is_file() |
هل يشير المسار إلى ملف عادي؟ | True لملف مثل notes.txt. |
is_dir() |
هل يشير المسار إلى مجلد؟ | True لمجلد مثل reports. |
توفر مكتبة pathlib هذه الدوال للتعامل الواضح مع المسارات. راجع أيضًا درس شرح مكتبة pathlib للتعامل مع مسارات الملفات والمجلدات لتفهم طريقة بناء المسارات باستخدام العامل /.
الحل الصحيح عند معرفة اسم الملف
عندما تعرف أن الملف المطلوب موجود داخل مجلد محدد، ابنِ مساره كاملًا بدل فتح اسم المجلد:
from pathlib import Path
reports_folder = Path("reports")
file_path = reports_folder / "summary.txt"
if file_path.is_file():
content = file_path.read_text(encoding="utf-8")
print(content)
else:
print("لم يتم العثور على الملف المطلوب.")
هذه الطريقة أفضل من كتابة مسار نصي طويل مثل "reports/summary.txt"، خصوصًا عندما تحتاج إلى بناء مسارات متعددة داخل مشروعك.
دالة آمنة لقراءة ملف نصي
في البرامج الحقيقية، لا تعتمد على الفحص فقط. قد يتغير المسار بين لحظة الفحص ولحظة القراءة، أو تحدث مشكلة في الصلاحيات. لذلك استخدم فحصًا واضحًا للمستخدم، ثم تعامل مع الأخطاء المحتملة بشكل محدد.
from pathlib import Path
def read_text_file(path_text):
path = Path(path_text)
if path.is_dir():
print(f"لا يمكن قراءة المجلد كملف: {path}")
return None
if not path.is_file():
print(f"الملف غير موجود: {path}")
return None
try:
return path.read_text(encoding="utf-8")
except PermissionError:
print("لا توجد صلاحية كافية لقراءة هذا الملف.")
except IsADirectoryError:
print("المسار أصبح مجلدًا أو يشير إلى مجلد بدل ملف.")
except UnicodeDecodeError:
print("تعذر قراءة الملف بترميز UTF-8.")
return None
content = read_text_file("reports/summary.txt")
if content is not None:
print(content)
لاحظ أننا لم نستخدم except Exception أو except: بلا تحديد. التقاط الأخطاء المحددة يجعل الرسالة أوضح ولا يخفي أخطاء برمجية أخرى تحتاج إلى إصلاح.
{alertWarning} تنبيه: لا تستخدمis_file()وحدها كبديل كامل عنtry/except. الفحص يقدّم رسالة مفهومة للمستخدم، لكن عملية القراءة نفسها يمكن أن تفشل لاحقًا بسبب الصلاحيات أو تغيير الملف أو المسار.
مثال عملي: قراءة الملفات النصية من مجلد
أحيانًا يكون هدفك فعلًا التعامل مع مجلد، لكنك تريد قراءة الملفات الموجودة بداخله. في هذه الحالة لا تستدعِ read_text() على المجلد نفسه؛ اعرض العناصر، ثم اقرأ الملفات فقط.
from pathlib import Path
folder = Path("reports")
if not folder.is_dir():
print("المجلد المطلوب غير موجود أو أن المسار ليس مجلدًا.")
else:
for item in folder.iterdir():
if item.is_file() and item.suffix.lower() == ".txt":
content = item.read_text(encoding="utf-8")
print(f"--- {item.name} ---")
print(content)
الكود السابق يتجاهل المجلد archive والملف chart.png، لأنه يقرأ العناصر التي تحقق شرطين معًا:
- العنصر ملف حقيقي:
item.is_file(). - امتداد الملف هو
.txt.
تستطيع لاحقًا تطوير المثال لقراءة ملفات CSV أو JSON، لكن لا تفترض أن كل عنصر داخل المجلد مناسب للقراءة بالطريقة نفسها.
الفرق بين IsADirectoryError وأخطاء قريبة منه
| الخطأ | معناه | مثال مختصر |
|---|---|---|
IsADirectoryError |
استخدمت مجلدًا في عملية تحتاج ملفًا. | Path("reports").read_text() |
FileNotFoundError |
المسار أو الملف المطلوب غير موجود. | Path("missing.txt").read_text() |
PermissionError |
لا توجد صلاحية كافية للوصول إلى الملف أو المجلد. | قراءة ملف محمي أو مفتوح بصلاحيات مقيدة. |
NotADirectoryError |
استخدمت ملفًا في عملية تحتاج مجلدًا. | محاولة استعراض ما داخل ملف كما لو كان مجلدًا. |
يمكنك الرجوع إلى حل خطأ FileNotFoundError عند فتح الملفات عندما لا يكون الملف موجودًا، وإلى حل خطأ PermissionError عند فتح الملفات عندما يكون المسار صحيحًا لكن المشكلة في الصلاحيات.
هل أستخدم os أم pathlib؟
يمكن حل المشكلة باستخدام os.path.isfile() وos.path.isdir() أيضًا، لكن pathlib مناسب جدًا للمبتدئ لأنه يجمع بناء المسار وفحصه وقراءته في كائن واحد.
import os
path = "reports"
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as file:
print(file.read())
elif os.path.isdir(path):
print("المسار مجلد وليس ملفًا.")
لا توجد مشكلة في استخدام os، خاصة إن كان مشروعك قديمًا ويستخدمه بالفعل. لكن لا تخلط الطريقتين بلا سبب داخل المثال نفسه؛ اختر أسلوبًا واضحًا وثابتًا.
أخطاء شائعة يجب تجنبها
1. فتح اسم المجلد بدل اسم الملف
لا تكتب Path("reports").read_text() إن كنت تريد قراءة ملف داخل المجلد. اكتب مسار الملف كاملًا مثل Path("reports") / "summary.txt".
2. الاعتماد على exists فقط
وجود المسار لا يثبت أنه ملف. استخدم is_file() أو is_dir() حسب ما تحتاجه.
3. استخدام except فارغ
try:
content = path.read_text()
except:
print("حدث خطأ.")
هذا الأسلوب يخفي سبب المشكلة. اكتب استثناءات محددة، مثل IsADirectoryError أو FileNotFoundError أو PermissionError، ثم اعرض رسالة مناسبة.
4. قراءة كل ما داخل المجلد بلا فلترة
داخل المجلد قد توجد صور ومجلدات فرعية وملفات مختلفة. افحص is_file() وفلتر الامتداد الذي تتوقعه قبل القراءة.
روابط داخلية مفيدة من بايثون العرب
- أساسيات بايثون 26: شرح مكتبة pathlib للتعامل مع مسارات الملفات والمجلدات
- أساسيات بايثون 22: التعامل مع الملفات
- أساسيات بايثون 31: شرح try وexcept والتعامل مع الأخطاء
- مشكلة وحل 13: حل خطأ FileNotFoundError
- مشكلة وحل 16: حل خطأ PermissionError
- بايثون بعد الأساسيات 2: تنظيم ملفات مشروع بايثون
الخلاصة
- يظهر
IsADirectoryErrorعندما تطلب من بايثون تنفيذ عملية ملف على مسار يشير إلى مجلد. exists()يثبت وجود المسار فقط، لكنه لا يحدد نوعه.- استخدم
is_file()قبل قراءة ملف، وis_dir()قبل استعراض محتوى مجلد. - عند قراءة ملف داخل مجلد، ابنِ مسار الملف كاملًا باستخدام
Path("folder") / "file.txt". - تعامل مع الأخطاء المحددة داخل
try/exceptبدل التقاط كل الأخطاء برسالة عامة. - عند استعراض مجلد، اقرأ الملفات المطابقة فقط واترك المجلدات والامتدادات غير المناسبة.
{alertSuccess} قاعدة عملية: قبل تنفيذ أي قراءة أو كتابة، اسأل: هل أتعامل مع ملف أم مجلد؟ هذا السؤال البسيط يمنع كثيرًا من أخطاء المسارات في مشاريع بايثون.
مصادر رسمية للتوسع
- توثيق IsADirectoryError في بايثون
- توثيق pathlib وPath في بايثون
- توثيق مكتبة os للتعامل مع نظام التشغيل
أسئلة شائعة
هل IsADirectoryError يعني أن المجلد غير موجود؟
لا. غالبًا يعني العكس: المسار موجود، لكنه مجلد بينما الكود كان يحتاج ملفًا. عندما لا يكون المسار موجودًا غالبًا ستواجه FileNotFoundError.
هل يكفي استخدام exists قبل read_text؟
لا. exists() لا يفرق بين الملف والمجلد. استخدم is_file() إذا كنت ستقرأ ملفًا، ثم تعامل مع الأخطاء المتوقعة عبر try/except.
كيف أقرأ ملفًا داخل مجلد؟
أنشئ مسارًا للملف نفسه، مثل Path("reports") / "summary.txt"، ثم تحقق من is_file() واقرأه باستخدام read_text() أو open().
هل أستخدم pathlib أم os لحل الخطأ؟
كلتاهما صالحتان. pathlib يقدم صياغة مرتبة لبناء المسارات وفحصها، بينما os.path مفيد في المشاريع التي تستخدمه مسبقًا. الأهم هو فحص ما إذا كان المسار ملفًا أو مجلدًا قبل العملية المناسبة.
لماذا أضع try وexcept بعد is_file؟
لأن الفحص يساعدك على إعطاء رسالة واضحة مبكرًا، لكنه لا يضمن أن القراءة ستنجح دائمًا. يمكن أن تتغير الصلاحيات أو حالة المسار قبل القراءة، لذلك يبقى التعامل المحدد مع الاستثناءات ضروريًا.



