الأخطاء هي طريقة بايثون لتخبرك بما حدث
كل خطأ في بايثون هو كائن له نوع ورسالة و traceback — أي سلسلة الاستدعاءات التي أوصلت إلى وقوع الخطأ. إتقان قراءة هذه المعلومات هو أهم مهارة منفردة يمكنك اكتسابها في تصحيح أخطاء بايثون. هذه الصفحة جولة في أنواع الأخطاء في بايثون التي ستقابلها فعلياً، مع العادات التي تجعل إصلاحها سريعاً.
لو تريد تعمّقاً في آلية عمل try/except وكيفية إطلاق استثناءاتك الخاصة، صفحة الاستثناءات تغطي الجانب النحوي. أما هنا فسنركز على الأخطاء تحديداً وكيف تقرأها.
كيف تقرأ الـ traceback في بايثون
جرّب تشغيل شيء يفشل:
def divide(a, b):
return a / b
def report(values):
for v in values:
print(divide(10, v))
report([5, 2, 0])
بايثون يطبع شيئًا مثل:
Human content to translate now:
Python prints something like:
Traceback (most recent call last):
File "script.py", line 8, in <module>
report([5, 2, 0])
File "script.py", line 6, in report
print(divide(10, v))
File "script.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
اقرأ التتبّع من الأسفل إلى الأعلى:
ZeroDivisionError: division by zero— نوع الاستثناء ورسالته. هذا هو سبب المشكلة.return a / bفي الدالةdivideبالسطر 2 — السطر الذي أطلق الخطأ فعليًا.print(divide(10, v))في الدالةreportبالسطر 6 — الاستدعاء الذي فجّر المشكلة.report([5, 2, 0])على مستوى الوحدة بالسطر 8 — نقطة البداية.
الإطار السفلي هو المكان الذي يوضع فيه الحل في أغلب الأحيان. وعندما ترمي مكتبةٌ ما خطأً من أعماقها الداخلية، اصعد في الـ traceback حتى تصل إلى أول إطار يخصّ كودك أنت — فهذا هو الاستدعاء الذي مرّرت له مدخلًا غير سليم.
أكثر أنواع الأخطاء في بايثون شيوعًا
NameError
"Name 'mesage' is not defined". سبب هذا الخطأ إمّا خطأ إملائي أو استخدام متغير قبل إسناد قيمة له. رسالة الخطأ في الإصدارات الحديثة من بايثون غالبًا ما تقترح عليك البديل الأقرب ("Did you mean 'message'?").
الحل: راجع الإملاء ونطاق المتغير (scope). إذا كان المتغير معرّفًا داخل دالة فقط، فلن تستطيع الوصول إليه من خارجها.
TypeError
"Can only concatenate str (not 'int') to str." العملية لا تقبل هذا النوع من البيانات. أمثلة كلاسيكية: جمع نص مع رقم، استدعاء شيء غير قابل للاستدعاء أصلاً، أو تمرير عدد خاطئ من المعاملات لدالة.
الحل: حوّل الأنواع بشكل صريح (str(30) أو int("30"))، أو تحقق مما تمرّره فعلياً. وفي الغالب تكون الـ f-string أوضح من استخدام + للدمج بين أنواع مختلفة: f"age: {30}".
ValueError
«Invalid literal for int() with base 10: 'hello'». النوع سليم فعلاً — الدالة int() تقبل سلسلة نصية — لكن القيمة نفسها غير صالحة. يظهر هذا الخطأ كثيراً مع int() و float()، وعند تحليل التواريخ، ومع أي دالة تتوقع قيمة ضمن نطاق محدد.
الحل: تحقق من القيمة قبل التحويل، أو التقط الخطأ وعالجه:
KeyError
"KeyError: 'charlie'" — يعني ببساطة إن المفتاح مش موجود في القاموس. فيه ٣ حلول شائعة، واختيار المناسب يعتمد على نيتك:
عند التعامل مع قاموس تريد أن يتم فيه إنشاء المفاتيح الناقصة تلقائيًا، فإن collections.defaultdict خيار يستحق التجربة.
IndexError
"List index out of range." طلبت عنصرًا في موقع غير موجود أصلًا داخل التسلسل.
الحل: تحقّق من طول القائمة قبل الوصول إليها، أو استخدم -1 للإشارة إلى آخر عنصر، أو اعتمد على التقطيع (slicing) مثل numbers[5:6] الذي يُرجع [] بدلًا من أن يرفع استثناءً.
AttributeError
"'NoneType' object has no attribute 'upper'". هنا استدعيت ميثود على شيء لا يملكها. في الغالب السبب أن المتغير من نوع غير متوقع — عادةً None بدل القيمة الحقيقية التي كنت تنتظرها.
الحل: ابحث عن المصدر الذي أرجع None. أسرع طريقة هي وضع print(type(var)) أو نقطة توقف (breakpoint) قبل السطر الذي يرمي الخطأ مباشرة. الدوال التي "تفشل أحياناً" تُرجع None في الغالب، فتأكد من قيمة الإرجاع قبل استدعاء أي ميثود عليها.
ModuleNotFoundError (و ImportError)
import fastapi
"No module named 'fastapi'." معناها ببساطة إن المكتبة مش متثبتة على مفسّر بايثون اللي بتشغّل السكربت عليه. في سببين شائعين لظهور الخطأ ده:
- المكتبة فعلاً مش متثبتة. شغّل الأمر ده في البيئة الصح:
python -m pip install fastapi. - ثبّتها بالفعل، بس في نسخة بايثون تانية. المشكلة دي بتحصل كتير على macOS لما
pipوpythonيكونوا بيشاوروا على نسختين مختلفتين.
الحل المضمون إنك تثبّت باستخدام نفس المفسّر اللي بتشغّل بيه الكود:
python -m pip install fastapi
إذا كنت تستخدم بيئة افتراضية (وهذا ما يُفترض بك فعله)، فتأكّد من تفعيلها قبل تنفيذ كلٍّ من pip install و python script.py.
FileNotFoundError
with open("settings.yaml") as f:
config = f.read()
"[Errno 2] No such file or directory: 'settings.yaml'." يعني ببساطة إن المسار مش موجود بالنسبة للمكان اللي بيشتغل فيه السكربت.
الحل: اطبع os.getcwd() في بداية السكربت عشان تتأكد فين بايثون بيدور فعلاً. استخدم مسارات مطلقة، أو اربط المسار بموقع السكربت نفسه عن طريق pathlib.Path(__file__).parent / "settings.yaml".
EOFError
name = input("Name: ")
"EOF when reading a line." الدالة input() حاولت تقرأ من المدخلات القياسية (stdin) وما لقيت شي — يا إن المدخلات جاية من مصدر فارغ عبر pipe، أو إنك ضغطت Ctrl-D عند شاشة الإدخال.
الحل: لو كان استخدام الـ pipe مقصود، غلّف الاستدعاء:
try:
name = input("Name: ")
except EOFError:
name = "anonymous"
في محرّر المتصفح الموجود داخل هذه التوثيقات، تتم محاكاة الإدخال من قِبل بيئة التشغيل، ولن يصادفك هذا الخطأ هناك.
خطأ IndentationError وخطأ SyntaxError
IndentationError: expected an indented block after function definition on line 2
هذه الأخطاء مميّزة — تظهر قبل أن يبدأ برنامجك بالعمل أصلاً، لأن بايثون فشل في تحليل الملف.
IndentationError— جسمdefأوifأوforوغيرها فارغ أو فيه مسافات بادئة غير منتظمة. معظم المحررات تُظهر المسافات البادئة بصرياً؛ فعّل خيار "show whitespace" حتى تكتشف الخلط بين المسافات (spaces) وعلامات الجدولة (tabs).SyntaxError— نسيت نقطتَي التركيز:، أو قوساً غير مغلق، أو كتبت كلمة مفتاحية بشكل خاطئ. إصدارات بايثون الحديثة تضع سهماً (^) تحت الحرف المسبّب للمشكلة.
الحل: رسالة الخطأ تذكر رقم السطر، فاذهب إليه مباشرة. وإذا لم تجد فيه شيئاً واضحاً، راجع السطر الذي قبله — فكثيراً ما يكون القوس المفتوح وغير المغلق في سطر سابق هو سبب ظهور خطأ الصياغة متأخراً.
RuntimeError
خطأ عام يجمع كل ما يحدث أثناء التشغيل ولا ينتمي لأي نوع محدد من الأخطاء الأخرى. أحد أشهر أنواعه الفرعية هو RecursionError (تجاوز الحد الأقصى لعمق الاستدعاء الذاتي). كما أن المكتبات الخارجية كثيراً ما ترفع RuntimeError حين تصل إلى حالة غير صالحة.
الحل: اقرأ الرسالة بتأنٍّ، فهي في الغالب واضحة وصريحة. وإذا كان السبب هو الاستدعاء الذاتي (recursion)، فحوّل الدالة إلى حلقة تكرارية عادية، أو في حالات نادرة استخدم sys.setrecursionlimit لرفع الحد.
تصحيح أخطاء بايثون باستخدام print
قبل أن تلجأ إلى مصحّح أخطاء حقيقي، تكفيك دالة print() — أو الأفضل منها صيغة f"{var=}" — للقبض على معظم الأخطاء:
f"{var=}" تطبع التعبير نفسه وقيمته معًا، فلا داعي لإعادة كتابة اسم المتغير داخل سلسلة التنسيق. شغّل السكربت، راجع المخرجات، وحدّد السطر الذي حصل فيه الخلل في القيمة. معظم الأخطاء "الغامضة" تتكشّف بعد ثلاث أو أربع جمل print موضوعة في الأماكن الصحيحة.
لا تنسَ حذف جمل print قبل عمل commit. واستخدام logging.debug(...) أفضل من print في الكود الذي ستنشره للإنتاج، لأنك تستطيع تفعيل سجلّ التصحيح وتعطيله دون تعديل الأسطر.
تصحيح أخطاء بايثون باستخدام breakpoint() و pdb
حين لا تكفي print، تنقلك breakpoint() مباشرة إلى مصحّح بايثون التفاعلي عند تلك النقطة:
def discount(price, percent):
breakpoint()
return price * (1 - percent / 100)
discount(100, 20)
شغّل السكربت في طرفية (terminal) حقيقية، وستجد نفسك أمام موجّه (Pdb). إليك بعض الأوامر التي تحتاج معرفتها:
p variable— لطباعة قيمة متغيّر.n— الانتقال إلى السطر التالي.s— الدخول داخل استدعاء الدالة.c— متابعة التنفيذ حتى نقطة التوقف التالية أو نهاية البرنامج.q— الخروج.l— عرض الأسطر المحيطة بالسطر الحالي من الشيفرة.
مصحّحات الـ IDE (مثل VS Code وPyCharm) تغلّف نفس البروتوكول داخل واجهة رسومية؛ تضع نقاط التوقف من هامش المحرر، وتظهر لك المتغيّرات في شريط جانبي. اختر ما تشعر أنه أقل احتكاكًا بالنسبة لك.
عادات تقلّل من الأخطاء التي تصادفك
- استخدم أسماء متغيّرات وصفية. نصف ضجيج
TypeErrorوAttributeErrorيختفي حين لا تنسى ما بداخل كل متغيّر. - تحقّق من المدخلات عند الحدود. افحص مدخلات المستخدم أو محتوى الملفات وحلّلها مرّة واحدة في بداية الدالة، حتى تثق بقية الشيفرة بالقيم الواردة إليها.
- اجعل الأخطاء ظاهرة، لا صامتة. استخدام
except Exception: passوحده يخفي الأخطاء التي تحتاج فعلاً إلى رؤيتها. التقط الاستثناءات المحدّدة، وعالجها بوعي، ودع البقية تنتشر إلى الأعلى. - اقرأ أسفل الـ traceback أولًا. كل دقيقة تنفقها في تعلّم قراءة الـ traceback في بايثون تعود عليك بأضعافها.
معظم أخطاء بايثون ليست ألغازًا — إنما أخطاء طباعية في سطر واحد، أو خطأ في نوع القيمة مع رسالة واضحة. ثق برسالة الخطأ؛ فهي في الغالب محقّة.
وصلت إلى النهاية
هنا ينتهي القسم المرجعي. قطعت الطريق من سؤال "ما هي بايثون؟" مرورًا بالمتغيّرات، والتحكّم في التدفّق، والمجموعات، والدوال، والأصناف، والتكرار، والتعامل مع البيانات الواقعية، وصولًا إلى الأخطاء. الخطوات التالية من هنا مشاريعية الطابع: اختر شيئًا تريد بناءه، ثم اعمل بشكل عكسي على القطع التي تحتاج إلى تعميق معرفتك بها. هذه الصفحات ستبقى في مكانها حين تعود بسؤال محدّد.
الأسئلة الشائعة
ما هو الخطأ KeyError في بايثون؟
يظهر KeyError عندما تحاول الوصول إلى مفتاح غير موجود في قاموس (dict). مثلاً users["missing"] سيرفع KeyError: 'missing'. البدائل الآمنة هي استخدام users.get("missing", default)، أو users.get("missing") التي تُرجع None، أو التحقق صراحةً بـ if key in users: قبل الوصول.
ما معنى EOFError في بايثون؟
EOFError (اختصار end-of-file) يظهر عندما تفشل دالة input() في قراءة أي شيء لأن مصدر الإدخال أُغلق. تقابله غالباً حين تشغّل سكربت يستخدم input() عبر pipe دون تمرير بيانات، أو حين تضغط Ctrl-D في الوضع التفاعلي. إذا كان سكربتك قد يستقبل إدخالاً من pipe، احمِ الكود بـ try/except EOFError.
ما سبب ظهور ModuleNotFoundError وكيف أحله؟
ModuleNotFoundError يعني أن import X لم يجد وحدة باسم X. إما أن الحزمة غير مثبتة أصلاً (الحل: pip install X)، أو أنها مثبتة على نسخة بايثون مختلفة عن التي تشغّل الكود (مشكلة شائعة جداً عند وجود أكثر من إصدار بايثون على الجهاز). الحل الأضمن هو python -m pip install X لأنه يستخدم نفس المفسّر الذي يشغّل كودك.
كيف أقرأ رسالة traceback في بايثون؟
اقرأها من الأسفل إلى الأعلى. السطر الأخير يوضح نوع الاستثناء ورسالته، أي ما حدث فعلياً. أما الأسطر التي فوقه فتمثّل مسار الاستدعاءات (call stack) الذي أوصل إلى الخطأ، وعادةً يكون كودك أنت قريباً من الأسفل. انتقل إلى الملف والسطر في أقرب إطار يخصّك — هناك بالضبط موضع الإصلاح.