Programming Basics SQL HTML CSS JavaScript React Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Linux Learn Typing

بايثونمعالجة الأخطاء

  • مفهوم معالجة الأخطاء
  • أنواع الأخطاء البرمجية
  • أمثلة على أنواع الأخطاء
  • الجمل try و except و finally و else
  • الإستثناءات الجاهزة
  • طباعة رسالة الخطأ الذي حدث
  • الكلمة المفتاحية raise

مفهوم معالجة الأخطاء

معالجة الأخطاء ( Exceptions Handling ) يقصد منها كتابة الكود الذي قد يسبب أي مشكلة في البرنامج بطريقة تضمن أنه إذا حدث الخطأ المتوقع أو أي خطأ آخر فإن البرنامج لن يعلّق أو يتم إغلاقه بشكل فجائي.

في هذا الدرس ستتعلم، كيف تحمي الكود من أي أخطاء قد تحدث، كيف تعالج الأخطاء إن وقعت، و كيف تقوم بتعريف أخطاء جديدة.

ظهور خطأ في البرنامج بشكل مفاجئ هو أمر سيئ جداً لأنه يؤدي إلى نفور عدد كبير من المستخدمين و عدم رغبتهم في العودة إلى استخدام هذا البرنامج مجدداً.

أنواع الأخطاء البرمجية

تنقسم الأخطاء البرمجية لثلاث أنواع رئيسية هي:

  • أخطاء لغوية ( Syntax Errors ) و التي تحدث عن مخالفة مبادئ اللغة مثل أن يتم تعريف شيء بطريقة خاطئة.
  • أخطاء تحدث أثناء التشغيل ( Runtime Errors ) و يقال لها إستثناءات ( Exceptions ) مما يؤدي إلى تعليقه و إيقافه بشكل غير طبيعي.
  • أخطاء منطقية ( Logical Errors ) و يقصد منها أن الكود يعمل بدون أي مشاكل و لكن نتيجة تشغيل هذا الكود غير صحيحة.

أي خطأ برمجي يحدث أثناء تنفيذ الكود يقال له إستثناء ( Exception ) حتى إن كان إسم الخطأ يحتوي على كلمة Error.
بمعنى آخر، أي Error يظهر أثناء تشغيل البرنامج يعتبر Exception.


بعض الأسباب التي تسبب حدوث إستثناء

  • في حال إدخال رقم index غير موجود في مصفوفة أو في متغير نصي.
  • في حال كان البرنامج يتصل بالشبكة و فجأةً إنقطع الإتصال.
  • في حال كان البرنامج يحاول قراءة معلومات من ملف نصي، و كان هذا الملف غير موجود.

الأخطاء الغير قابلة للمعالجة

في حال كان الكود يحتوي على أخطاء لغوية ( Syntax Errors ) فيجب إصلاحها كلها قبل تجربة الكود حتى يستطيع مفسر الكود أن ينفذ كل الأوامر الموجودة في الكود و إن لم تفعل ذلك فإنه عندما يحاول تنفيذ الأمر المكتوب بشكل خاطئ سيفشل و لن يتابع تنفيذ باقي الأوامر.

لا يمكنك حماية الكود من الأخطاء اللغوية بل يمكنك حمايته من الإستثناءات ( Exceptions ) التي قد تحدث وقت تنفيذ الكود.

أمثلة على أنواع الأخطاء

من خلال الأمثلة التالية ستتعرف على جميع أنواع الأخطاء البرمجية.


مثال يتضمن خطأ لغوي ( Syntax Error )

في المثال التالي وضعنا قوس إضافي في الدالة الطباعة عمداً للتسبب بخطأ حيث أننا كتبنا print()) بدلاً من print().

مثال

Test.py
x = 10
print(x))

النتيجة

File "C:/Users/Mhamad/PycharmProjects/myapp/Test.py", line 2
    print(x))
            ^

SyntaxError: invalid syntax

برنامج PyCharm يظهر خط باللون الأحمر لينبه أنه يوجد خطأ لغوي قبل تشغيل البرنامج.
في حال تشغيل البرنامج بدون إصلاح الخطأ سيقوم مفسر لغة بايثون بوضع سهم أحمر ^ ليخبرنا أين وجد خطأ لغوي في الكود عندما حاول تنفيذه.


أمثلة تتضمن إستثناءات ( Exceptions )

في المثال التالي قمنا بطباعة قيمة متغير لم نقم أصلاً بإعطاءه قيمة!
هنا سيحدث الخطأ وقت التشغيل عندما يكتشف مفسّر لغة بايثون أن المتغير لا يحتوي على قيمة.

المثال الأول

Test.py
print(x)

النتيجة

File "C:/Users/Mhamad/PycharmProjects/myapp/Test.py", line 1, in <module>

NameError: name 'x' is not defined

في المثال التالي قمنا بتعريف list إسمه aList، و يتألف من 4 عناصر.
بعدها حاولنا طباعة قيمة كل عنصر فيه و حاولنا طباعة قيمة عنصر غير موجود!
هنا سيحدث الخطأ وقت التشغيل عندما يكتشف مفسّر لغة بايثون أنه لا يوجد عنصر يملك Index يساوي 4 في الكائن aList.

المثال الثاني

Test.py
# يتألف من 4 عناصر aList إسمه list هنا قمنا بتعريف
aList = [10, 20, 30, 40]

# aList هنا قمنا بطباعة قيم عناصر الكائن
print(aList[0])
print(aList[1])
print(aList[2])
print(aList[3])

# و هذا ما سيؤدي لحدوث خطأ برمجي وقت التشغيل هنا قمنا بطباعة قيمة عنصر غير موجود أصلاً في الكائن
print(aList[4])

النتيجة

10
20
30
40
File "C:/Users/Mhamad/PycharmProjects/myapp/Test.py", line 8, in <module>
    print(aList[4])

IndexError: index out of range

مثال يتضمن خطأ منطقي ( Logical Error )

في المثال التالي قمنا بإنشاء برنامج يطبع للطالب ما إذا كان ناجحاً أو راسباً بناءاً على معدله النهائي.
من المفترض أنه يتم إعتبار الطالب راسب في حال كان معدله بين 0 و 9.9. و يتم إعتباره ناجح في حال كان معدله 10 و 20.
الخطأ المنطقي الذي وضعناه هو أننا عند طباعة نتيجة الطالب لم نتأكد ما إذا كان المعدل بين 0 كحد أدنى و 20 كحد أقصى.

مثال

Test.py
average = 25

if average < 10:
    print('The student failed the exam')

elif average >= 10:
    print('The student passed the exam')

النتيجة

The student passed the exam

نلاحظ أنه لا يوجد مشكلة برمجية سببت إيقاف تنفيذ الكود لكننا نعلم أنه يوجد مشكلة منطقية في الكود.
المشكلة المنطقية هنا هي أن المعدل الذي تم على أساسه طباعة جملة النجاح هو معدل مستحيل أن يكون حقيقي.

الجمل try و except و finally و else

نستخدم هذه الجمل للأسباب التالية:

  • أي كود تشك بأنه قد يسبب خطأ يجب وضعه بداخل بلوك الجملة try لضمان أن لا يعلق البرنامج أو يظهر خطأ مفاجئ أثناء التشغيل.
  • أي كود تريد تنفيذه لمعالجة الخطأ الذي حدث في الجملة try تضعه بداخل بلوك الجملة except.
  • أي كود تريد تنفيذه في حال لم يحدث خطأ في الجملة try تضعه بداخل بلوك الجملة else.
  • أي كود تريد تنفيذه سواء حدث أو لم يحدث خطأ في الجملة try تضعه بداخل بلوك الجملة finally.

بمجرد أن تضع الكود بداخل try ستكون مجبراً على وضع الجملة except أو الجملة finally بعدها أو وضع كلا الجملتين معاً. و للعلم فإن برنامج PyCharm سيظهر لك تنبيه بمجرد أن تضع الكود بداخل try يخبرك فيه أنك يجب أن تضع إحدى هاتين الجملتين بعدها.

بعد الجملة except يمكنك وضع الجملة finally أو الجملة else إن أردت لكن لا يمكنك وضع كلاهما في وقت واحد.

في حال كنت تكتب كود يمكن أن يسبب عدة أنواع من المشاكل، يمكنك وضع أكثر من جملة except حتى تعالج كل نوع من المشاكل التي قد تحدث على حدا.


في حال أردت استخدام الجمل try و except و finally سيكون شكل الكود كالتالي.

try:
    # Something
except:
    # Handle Errors
finally:
    # Optional Clean Up Code

في حال أردت استخدام الجمل try و except و else فسيكون شكل الكود كالتالي.

try:
    # Something
except:
    # Handle Errors
else:
    # If No Errors, Do Extra Things

في المثال التالي إستخدمنا الجملتين try و except و لم نضع أي خطأ متعمد في الكود.
تذكر: بما أنه لن يحدث أي خطأ بداخل الجملة try فهذا يعني أنه لن يتم تنفيذ أي أمر موضوع في الجملة except. و بعدها سيتم إكمال تنفيذ أي أوامر موضوعة في البرنامج.

المثال الأول

Test.py
x = 10

# except و بما أنه لا يوجد أي مشكلة هنا فهذا يعني أنه لن يتم تنفيذ أمر الطباعة الموضوع في الجملة ،x حاولنا طباعة قيمة try في الجملة
try:
    print('x =', x)
except:
    print('An exception occurred')

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و لكن تم معالجتها
print('Program still work')

النتيجة

x = 10
Program still work

في المثال التالي إستخدمنا الجملتين try و except و وضعنا خطأ متعمد في الكود.
تذكر: بما أنه سيحدث خطأ بداخل الجملة try فهذا يعني أنه سيتم الإنتقال إلى الجملة except عند حدوث الخطأ. و بعدها سيتم تنفيذ الأوامر الموضوعة فيها، و من ثم إكمال تنفيذ أي أوامر موضوعة في البرنامج.

المثال الثاني

Test.py
# و الذي لم نخزن أي قيمة فيه بعد. بما أن هذا الأمر سيؤدي لحدوث خطأ x حاولنا طباعة قيمة try في الجملة
# و تنفيذ أي أمر موضوع فيها except و الإنتقال إلى الجملة try فهذا يعني أنه سيتم الخروج من الجملة
try:
    print('x =', x)
except:
    print('An exception occurred')

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

An exception occurred
Program still work

في المثال التالي إستخدمنا الجملتين try و except و وضعنا خطأ متعمد في الكود.
ملاحظة: هنا توقعنا حدوث خطأ محدد و إمكانية حدوث أي خطأ آخر.

المثال الثالث

Test.py
# و الذي لم نخزن أي قيمة فيه بعد و بالتالي سيحدث خطأ x حاولنا طباعة قيمة try في الجملة
try:
    print('x =', x)
    
# سيتم تنفيذ الكود التالي - NameError إذا كان سبب الخطأ هو محاولة عرض قيمة متغير لم يتم تعريفه سابقاً - أي خطأ من النوع
except NameError:
    print('x is not defined')

# سيتم تنفيذ الكود التالي - NameError إذا كان سبب الخطأ هو خطأ من أي نوع آخر - غير النوع
except:
    print('An exception occurred')

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

x is not defined
Program still work

في المثال التالي إستخدمنا الجمل الثلاثة try و except و finally و لم نضع أي خطأ متعمد في الكود.
تذكر: الجملة finally يتم تنفيذ أي أمر موضوع فيها سواء حدث خطأ أو لم يحدث.

المثال الرابع

Test.py
x = 10

try:
    print('x =', x)
    
except:
    print('An exception occurred')

# try سيتم تنفيذه في حال حدوث أو عدم حدوث خطأ في الجملة finally أي أمر موضوع في الجملة
finally:
    print('Finally block always executed')

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

x = 10
Finally block always executed
Program still work

في المثال التالي إستخدمنا الجمل الثلاثة try و except و else و لم نضع أي خطأ متعمد في الكود.
تذكر: الجملة else يتم تنفيذ الأوامر الموضوعة فيها في حال لم يحدث خطأ فقط.

المثال الخامس

Test.py
x = 10

try:
    print('x =', x)
    
except:
    print('An exception occurred')
    
# try يتم تنفيذها فقط في حال عدم حدوث خطأ في الجملة else الأوامر التي نضعها في الجملة
else:
    print('Else block executed only when no exception occurred')

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

x = 10
Else block executed only when no exception occurred
Program still work

الإستثناءات الجاهزة

تم تقسيم الإستثناءات أو الأخطاء الأساسية في بايثون إلى عدة أنواع و كل نوع تم تمثيله في كلاس خاص.
جميع هذه الكلاسات ترث من كلاس أساسي إسمه BaseException. و هذا يعني أنك إذا أردت بناء إستثناء جديد سيكون عليك إنشاء كلاس يرث من هذا الكلاس أو من أحد الكلاسات التي ترث منه.

إذاً، أي كلاس يرث من الكلاس BaseException هو كلاس يمثل إستثناء معين.

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning

طباعة رسالة الخطأ الذي حدث

عندما يحدث خطأ في الجملة try، يقوم مفسر بايثون بإنشاء كائن يمثل نوع الخطأ الذي حدث في هذه الجملة.
بعدها يمر على كل جملة except موضوعة بعدها تباعاً و يقارن نوع الكائن الذي تم إنشاؤه في الجملة try مع نوع الخطأ (الذي هو في الأصل عبارة عن إسم الكلاس الذي يمثل الخطأ المتوقع حصوله) المحدد في كل جملة except حتى يجد الجملة التي تعالج هذا النوع من الخطأ و ينفذ الأوامر الموضوعة فيها.

الآن، في حال أردت طباعة رسالة الخطأ الذي حدث في الجملة try و التي بدورها أرسلته إلى الجملة except فكل ما عليك فعله هو إستقبال رسالة الخطأ من هذا الكائن و وضعها في متغير بواسطة الكلمة المفتاحية as.


المثال التالي يعلمك طريقة طباعة رسالة الخطأ الجاهزة في الكائن الذي يمثل الخطأ المحدد الذي قد يحدث.

المثال الأول

Test.py
# لأننا حاولنا طباعة قيمة متغير لا يملك قيمة أصلاً NameError الكود هنا يسبب خطأ من النوع
try:
    print('x =', x)

# و من ثم عرضها msg سيتم تخزين رسالة الخطأ الجاهزة في هذا الكائن و وضعها بشكل مؤقت في المتغير NameError هنا قلنا أن أي خطأ نوعه
except NameError as msg :
    print('Error Message:', msg)

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

Error Message: name 'x' is not defined
Program still work

المثال التالي يعلمك طريقة طباعة رسالة الخطأ الجاهزة مهما كان نوع الخطأ الذي حدث في الجملة try.
ملاحظة: قمنا بإعادة المثال السابق مع تبديل الكلاس NameError بالكلاس BaseException.
الفكرة الأساسية هنا هي أنه بما أن الكلاس BaseException يعتبر الكلاس الأساسي لأي خطأ قد يحدث، فهذا يعني أنه يمكننا إعتبار الخطأ الذي حدث عبارة عن كائن منه لأنه سيكون من كلاس يرث منه.

المثال الثاني

Test.py
# لأننا حاولنا طباعة قيمة متغير لا يملك قيمة أصلاً NameError الكود هنا يسبب خطأ من النوع
try:
    print('x =', x)

# و من ثم عرضها msg سيتم تخزين رسالة الخطأ الجاهزة في هذا الكائن و وضعها بشكل مؤقت في المتغير NameError هنا قلنا أن أي خطأ نوعه
except BaseException as msg :
    print('Error Message:', msg)

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

Error Message: name 'x' is not defined
Program still work

الكلمة المفتاحية raise

لجعل الدالة ترمي إستثناء في حال حدوث خطأ معين، يجب جعلها تفعل raise لكائن من الكلاس Exception يحتوي على رسالة الخطأ التي تريد إظهارها.


في المثال التالي قمنا ببناء دالة ترمي إستثناء في حال تم تمرير قيمة أصغر من 0 لها عند استدعائها.
بعدها قمنا باستدعاء الدالة و تمرير قيمة أصغر من 0 لها فتسبب ذلك بإيقاف البرنامج.

المثال الأول

Test.py
# تأخذ قيمة عند استدعائها func هنا قمنا بتعريف دالة إسمها
# إذا تم تمرير قيمة أصغر من 0 لها, ترمي إستثناء. غير ذلك تطبع القيمة التي تم تمريرها لها
def func(x):
    if x < 0:
        raise Exception("Error: Passed value can't be negative")

    print(x, "Acceptable value")


# و تمرير قيمة أكبر من 0 لها. نلاحظ أنها تقوم بطباعتها و لا يسبب ذلك أي مشكلة في الكود func() هنا قمنا باستدعاء الدالة
func(5)

# و تمرير قيمة أصغر من 0 لها. نلاحظ أن هذا الأمر سبب مشكلة في الكود و أدى إلى إيقاف البرنامج func() هنا قمنا باستدعاء الدالة
func(-1)

# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

5 Acceptable value
File "C:/Users/Mhamad/PycharmProjects/myapp/Test.py", line 14, in <module>
    func(-1)
File "C:/Users/Mhamad/PycharmProjects/myapp/Test.py", line 5, in func
    raise Exception("Error: Passed value can't be negative")

Exception: Error: Passed value can't be negative

في المثال التالي قمنا ببناء دالة ترمي إستثناء في حال تم تمرير قيمة أصغر من 0 لها عند استدعائها.
بعدها قمنا باستدعاء الدالة بداخل بلوك try مع تمرير قيمة أصغر من 0 لها و من ثم معالجة الخطأ الذي ستسببه بداخل بلوك except.

المثال الثاني

Test.py
# تأخذ قيمة عند استدعائها func هنا قمنا بتعريف دالة إسمها
# إذا تم تمرير قيمة أصغر من 0 لها, ترمي إستثناء. غير ذلك تطبع القيمة التي تم تمريرها لها
def func(x):
    if x < 0:
        raise Exception("Error: Passed value can't be negative")

    print(x, "Acceptable value")


# مرتين func() هنا قمنا بمحاولة إستدعاء الدالة
try:
    func(5)
    func(-1)

# هنا قمنا بالتقاط أي خطأ قد يحدث سببه أن القيمة التي تم تمريرها للدالة أصغر من 0, و من ثم طباعة رسالة الخطأ
except Exception as msg:
    print(msg)


# سيتم تنفيذ أمر الطباعة التالي في حال كان البرنامج لا يوجد فيه أي مشكلة أو حدثت مشكلة سابقاً و تم معالجتها
print('Program still work')

النتيجة

5 Acceptable value
Error: Passed value can't be negative
Program still work