الاستثناء (Exception) هو خروج البرنامج عن المسارات المعتادة إلى مسار لم تتم برمجته. وهو أحد أنواع خطأ وقت التشغيل (Runtime Error)؛ أي التي تحصل بعد تشغيل البرنامج وأثناء عمله، لا في وقت تفسيره. ومن أمثلة ذلك:
أن يؤمَر بقراءة ملف .. والواقع أن هذا الملف غير موجود!
أو أن يطلب من المستخدم رقمًا فيعطيه كلاماً!
أو أن يطلب من الشبكة شيئًا .. فتنقطع الشبكة!
فكل هذه تعتبر مسارات غير مثالية لكنها تحصل في ظروف واقعيَّة. فيجب كتابة قطع في البرنامج تتعامل معها. ولذا فإن بعض الممارسين لا يفضلون استعمال كلمة استثناء لأنَّ مثل ذلك يحصل كثيرًا فهو ليس خارجًا عن العادة؛ بل من الطبيعي أن يحصل ذلك في الواقع!
تغير سير الأوامر عند الخطأ
تنفذ التعليمات في لغة البرمجة الأمرية (Imperative) كبايثون بحسب ترتيبها (من الأعلى إلى الأسفل). لكن عند حدوث خطأ، يتغيَّر سيْر الأوامر باستعمال جملة try-except. وشكل جملة التعامل مع الخطأ على هذا النحو:
المحاولة: try تتضمن الجملة التي نتوقع حدوث خطأٍ فيها
المطابقة: except Exception هي مثل if تنفذ ما تتضمنه إن كان الخطأ من نوع Excpetion
عدم المطابقة: else تعمل عند عدم الخطأ
المتابعة: finally وهي جُملة تعمل سواء وقع الخطأ أم لم يقع؛ لكنَّ بايثون تضمن عملها إن حصل خطأ أثناء التعامل مع الخطأ
print('enter')try:# حاول تشغيل هذه القطعةexceptExceptionas e:# شغل هذه القطعة عند الخطأelse:# وإن لم يحصل فهذه القعطةfinally:# وهذه تشتغل سواء حصل الخطأ أم لا# وفائدتها أنها تعمل قبل رجوع الخطأ لموضع النداءprint('exit')
أنواع الاستثناء
تم تعريف أنواع من الخطأ في بايثون متبوعة بكلمة Error، وذلك باعتبار حالات خطأ نمطية ومتكررة:
SyntaxError
السبب: خطأ نحوي في صياغة اللغة:
كلمة غير صحيحة: خطأ في الإملاء
في وضع كلمة صحيحة في غير سياقها
محاذاة غير متسقة (IndentationError)
الحل: اقرأ رسالة الخطأ وستدلُّك على السبب والموضع الذي حصل فيه الخطأ.
TypeError
السبب:
طلب فعل بعدد أكثر أو أقل من العوامل الواجبة (مثل: len(1, 2))
طلب فعل بعوامل لا تطابق النوع المحدد في تعريفه (مثل: math.sqrt('nine') أو 5 + '5')
الحل: الوقاية بـ type() أو isinstance() أو بالتأكد من تحويل النوع مسبقًا.
a =5b =input('Enter a number: ')result = a +int(b)
ValueError
السبب: أن يكون النوع صحيحًا (فلا يحصُل TypeError) لكن القيمة غير مقبولة.
مثلاً: طلب فعل بقيمة نوعها عددي لكنَّها سالبة وهو لا يقبل إلا الموجبة. نحو: math.sqrt(-16) فالجذر التربيعي لا يقبل السالب.
الحل: الوقاية بفحص مدى القيمة ، نحو: if x >=0: math.sqrt(x)
IndexError & KeyError
السبب: الرقم الذي استعمل في عملية الإشارة [index]list (قائمة) أو dict[key] (قاموس) يشير لما هو خارج المجموعة.
نحو:
my_list = [10, 20, 30]idx =3value = my_list[idx]
---------------------------------------------------------------------------IndexError Traceback (most recent call last)
Cell In[1], line 3 1 my_list = [10, 20, 30]
2 idx =3----> 3 value =my_list[idx]IndexError: list index out of range
الحل بالوقاية:
if idx <len(my_list): value = my_list[idx]else:# do something else
أو بالعلاج:
try: value = my_list[idx]exceptIndexError:# do something else
if key in my_dict: value = my_dict[key]else:# do something else
أو هكذا (تعيين قيمة ابتدائية):
value = my_dict.get(key, 0)
أو بالعلاج:
try: value = my_dict[key]exceptKeyError:# do something else
AttributeError & NameError
السبب: استعمال متغير أو فعل قبل تعريفه.
فإن أسنِد إلى كائن؛ وقع AttributeError
وإلا وقع NameError
a =10a + X
---------------------------------------------------------------------------NameError Traceback (most recent call last)
Cell In[3], line 2 1 a =10----> 2 a +XNameError: name 'X' is not defined
some_function(55)
---------------------------------------------------------------------------NameError Traceback (most recent call last)
Cell In[4], line 1----> 1some_function(55)
NameError: name 'some_function' is not defined
class A:passa = A()a.x
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)
Cell In[5], line 5 2pass 4 a = A()
----> 5a.xAttributeError: 'A' object has no attribute 'x'
a.do_something()
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)
Cell In[6], line 1----> 1a.do_something()
AttributeError: 'A' object has no attribute 'do_something'
ModuleNotFoundError
السبب: فشل جُملة الاستيراد import numpy
الحل:
تأكد من صحة الإملاء
تأكد من تثبيت الوحدة في البيئة التي يعمل فيها البرنامج: pip install numpy
تعريف أخطاء جديدة
تعريف الخطأ يكون بتعريف نوع جديد يرث من النوع Exception، وهذا ما يحققه السطر الأول بين القوسين. وتستطيع أن ترث ممن يرث، فتتكون لديك فروع من هذا الخطأ:
class ParentError(Exception):passclass XError(ParentError):passclass YError(ParentError):pass
flowchart TD
A[ParentError] --> B[XError]
A --> C[YError]
وهذا الإجراء يحصل فيه الخطأ بطريقة مصطنعة لكنها توضح ما نريد، وهو الخطأ الفرعي XError الذي يرث من الخطأ الأصلي ParentError:
def do_something():raise XError('Something went wrong')
ثم حين نفحص، تسطيع أن نطابق بالأصل أو الفرع:
try: do_something()except ParentError as e:print("caught you:", e)