الإجابة المختصرة
للتحقق من وجود ملف، استخدم os.path.isfile() من المكتبة القياسية. تُرجع True عندما يشير المسار إلى ملف عادي موجود، وFalse في غير ذلك. ولا تطلق أي خطأ إذا كان المسار غير موجود.
تُحسب المسارات نسبةً إلى المجلد الذي يعمل فيه البرنامج (مجلد العمل الحالي)، إلا إذا مرّرت مسارًا مطلقًا مثل /home/ada/notes.txt أو C:\Users\Ada\notes.txt.
يتناول باقي هذه الصفحة الطرق الأخرى لطرح السؤال نفسه، والفروق بينها، والحالة التي لا ينبغي أن تطرحه فيها أصلًا.
os.path.exists مقابل isfile مقابل isdir
تحتوي الوحدة os.path على ثلاث دوال تحقق متقاربة، وكل منها تجيب عن سؤال مختلف قليلًا:
| الدالة | تُرجع True عندما يكون المسار |
|---|---|
os.path.exists(p) | أي شيء موجود: ملفًا، أو مجلدًا، أو رابطًا رمزيًا صالحًا |
os.path.isfile(p) | ملفًا عاديًا موجودًا |
os.path.isdir(p) | مجلدًا موجودًا |
يظهر الفرق بمجرد أن يدخل مجلد في الصورة:
المخرجات:
reports True False True
reports/summary.txt True True False
reports/old.txt False False False
os.path.exists("reports") تساوي True مع أن reports مجلد. فإذا كان الكود على وشك قراءة المسار على أنه ملف، فإن exists هي الدالة الخطأ: المجلد سيجتاز التحقق، ثم يفشل open("reports") (بخطأ IsADirectoryError على Linux وmacOS، وPermissionError على Windows). اختر isfile حين تقصد ملفًا، وisdir حين تقصد مجلدًا.
تفصيلان آخران عن دوال os.path الثلاث:
- تتبع الدوال الرابط الرمزي. الرابط الذي يشير إلى ملف حقيقي يُحتسب ملفًا، أما الرابط المعطوب (الذي حُذف هدفه) فتُرجع معه الدوال الثلاث
False. - إذا لم يكن مسموحًا لبايثون بالاطلاع على محتوى مجلد، تُرجع الدالة
Falseبدلًا من إطلاقPermissionError. أي أنFalseتعني "غير موجود أو لا يمكن الوصول إليه"، وليس "غير موجود" بالمعنى الدقيق.
التحقق باستخدام pathlib
pathlib هي واجهة المسارات الكائنية في المكتبة القياسية، ومعظم الكود الحديث يستخدمها. يملك الكائن Path دوال التحقق الثلاث نفسها على شكل توابع:
تعمل Path.exists() وPath.is_file() وPath.is_dir() كما تعمل os.path.exists وisfile وisdir. ويربط المعامل / أجزاء المسار بالفاصل المناسب لنظام التشغيل، فتعمل folder / "summary.txt" على Windows وmacOS وLinux على حد سواء. راجع صفحة التعامل مع الملفات للقراءة والكتابة عبر كائنات Path.
أيهما تستخدم؟ إذا كان باقي الكود يمرّر كائنات Path أصلًا، فابقَ مع pathlib. وإذا كنت تعمل بنصوص عادية ودوال os، فإن os.path.isfile صحيحة بالقدر نفسه. كلاهما يعطي الإجابات نفسها.
لماذا قد يكون التحقق المسبق خطوة خاطئة
السبب الأكثر شيوعًا للتحقق من وجود ملف هو فتحه بعد ذلك:
import os
if os.path.isfile("config.txt"):
with open("config.txt") as f: # the file might be gone by now
settings = f.read()
else:
settings = ""
في هذا الكود ثغرة. بين استدعاء isfile() واستدعاء open() قد يحذف برنامج آخر، أو خيط (thread) آخر، أو المستخدم نفسه الملف أو يغيّر اسمه. التحقق قال True، ومع ذلك يفشل الفتح، وينهار البرنامج بخطأ FileNotFoundError في كل الأحوال. تُسمى هذه الثغرة حالة تسابق (race condition)، أو "من وقت التحقق إلى وقت الاستخدام" (time of check to time of use). هي نادرة على حاسوبك الشخصي، وأقل ندرة بكثير على خادم مزدحم تتشارك فيه عدة عمليات مجلدًا واحدًا.
الحل هو الاستغناء عن التحقق المنفصل وترك open() تقوم بدور التحقق. تطلق بايثون FileNotFoundError عندما يكون الملف مفقودًا، وأنت تعالج ذلك في except:
المخرجات:
theme=dark
(no settings file, using defaults)
صارت لدينا عملية واحدة بدلًا من اثنتين، فلا يبقى شيء نتسابق معه. لهذا الأسلوب اسم في مجتمع بايثون: EAFP، أي "طلب الصفح أسهل من طلب الإذن" (easier to ask forgiveness than permission). والأسلوب المعاكس، أي التحقق أولًا، هو LBYL، أي "انظر قبل أن تقفز" (look before you leap). تميل بايثون إلى EAFP في العمل مع الملفات، لأن نظام التشغيل هو الوحيد الذي يعرف الإجابة لحظة فتح الملف.
FileNotFoundError ليس الشيء الوحيد الذي قد يسوء. المسار الذي هو مجلد يطلق IsADirectoryError (وPermissionError على Windows)، والملف الذي لا تملك صلاحية قراءته يطلق PermissionError. والثلاثة كلها أصناف فرعية من OSError، لذا يلتقط except OSError: كل حالات "تعذّر فتحه" دفعة واحدة. تشرح صفحة الاستثناءات كيف تختار بين التقاط خطأ واحد بعينه والتقاط عائلة كاملة من الأخطاء.
متى يكون التحقق البسيط مقبولًا
التحقق المسبق ليس خطأً في كل الحالات. إنه الأداة الصحيحة حين تحتاج الإجابة وحدها ولن تتصرف في الملف في السطر التالي:
- اختيار الرسالة التي تعرضها ("عُثر على 3 ألعاب محفوظة")
- التحقق من مسار كتبه المستخدم قبل بدء مهمة طويلة
- تخطي عمل أُنجز من قبل، حيث لا تكلّفك الإجابة القديمة سوى تكرار العمل
القاعدة العامة: إذا كنت ستفتح الملف أو تقرؤه أو تكتب فيه أو تحذفه، فافعل ذلك داخل try. وإذا كنت تريد المعرفة فقط، فإن isfile() أو Path.is_file() كافية.
إنشاء ملف فقط إذا لم يكن موجودًا
يظهر السؤال المعاكس أيضًا: "اكتب هذا الملف، لكن لا تستبدل ملفًا موجودًا أبدًا". والنسخة البديهية فيها التسابق نفسه كما في السابق:
if not os.path.exists("report.txt"):
with open("report.txt", "w") as f: # another process may create it first
f.write("new report\n")
الوضع "x" (الإنشاء الحصري) ينفّذ الخطوتين في خطوة واحدة. فهو يُنشئ الملف، أو يطلق FileExistsError إذا كان الاسم مستخدمًا:
المخرجات:
attempt 1: created report.txt
attempt 2: report.txt already exists, left it alone
أما المجلدات، فإن os.makedirs(path, exist_ok=True) وPath(path).mkdir(parents=True, exist_ok=True) تُنشئان المجلد وأي مجلدات أب مفقودة، ولا تفعلان شيئًا إذا كان موجودًا بالفعل. لا حاجة إلى استدعاء isdir() أولًا.
أخطاء شائعة
- استخدام
exists()وأنت تقصد ملفًا. المجلد الذي يحمل الاسم نفسه يجتاز التحقق. استخدمisfile()أوPath.is_file(). - نسيان نقطة بداية المسارات النسبية. يُبحث عن
"data.txt"في مجلد العمل الحالي، وهو المجلد الذي شغّلت منه البرنامج، وليس بالضرورة المجلد الذي يحتوي على السكربت. للعثور على ملف بجوار السكربت، ابنِ المسار منPath(__file__).parent. - التحقق ثم الفتح. قد يتغير الملف في هذه الأثناء. افتحه داخل
tryوعالجFileNotFoundError. - التقاط كل الاستثناءات. وضع
except Exception:حولopen()يُخفي أيضًا الأخطاء المطبعية والعلل في الكود الذي يقرأ الملف. التقطFileNotFoundErrorأوOSError.
الأسئلة الشائعة
كيف أتحقق من وجود ملف في بايثون؟
استورد os واستدعِ os.path.isfile("data.txt")، وهي تُرجع True فقط إذا كان المسار ملفًا عاديًا موجودًا. وفي pathlib تكتب Path("data.txt").is_file(). استخدم os.path.exists() أو Path.exists() إذا كان المجلد الذي يحمل الاسم نفسه يجب أن يُحتسب أيضًا.
ما الفرق بين os.path.exists وos.path.isfile؟
os.path.exists(p) تُرجع True لأي شيء موجود في ذلك المسار: ملف، أو مجلد، أو رابط رمزي (symlink) يشير إلى شيء حقيقي. أما os.path.isfile(p) فتُرجع True للملف العادي فقط، لذا تُرجع False مع المجلد. ونظيرتها الخاصة بالمجلدات هي os.path.isdir(p).
هل يجب أن أتحقق من وجود الملف قبل فتحه؟
غالبًا لا. قد يُحذف الملف أو يُنشأ في الفترة بين التحقق واستدعاء open()، فلا يضمن التحقق شيئًا. افتح الملف مباشرة داخل try وعالج FileNotFoundError في except. أما التحقق المنفصل فهو الخيار الصحيح حين تحتاج الإجابة وحدها، كأن تقرر أي رسالة تعرضها.
كيف أنشئ ملفًا فقط إذا لم يكن موجودًا؟
افتحه بالوضع "x": open("report.txt", "x"). تُنشئ بايثون الملف، أو تطلق FileExistsError إذا كان هناك شيء بالاسم نفسه. يحدث التحقق والإنشاء في خطوة واحدة، فلا يستطيع أي برنامج آخر أن يتدخل بينهما.