دورة Python للمبتدئين

8 : البرمجة بلغة بايثون – معالجة الاستثناءات

Python3.8

السلام عليكم و رحمة الله تعالى و بركاته

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

لكن قبل ذلك، يجب أن نتعرف أولا على الأخطاء و نوعها و سبب ظهورها:

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

عامة، تُقسم الأخطاء البرمجية في لغات البرمجة إلى 3 أنواع:

  • الأخطاء البنيوية Syntax Errors : أي الأخطاء المتعلقة ببناء الجملة. فَلِكل لغة برمجية كتابتها الخاصة و قواعدها المميزة، إذا لم يتم احترام قاعدة ما في الكتابة سيظهر هذا الخطأ كما تلاحظون في المثال التالي:

عدم كتابة الجملة بين قوسين في الدالة input يعتبر خطأ في بناء الجملة Syntax Error.

  • الأخطاء المنطقية Logical Errors : تحدث عندما تظهر نتيجة غير منطقية أو خاطئة، و ذلك نتيجة عدم حسن استعمال المعاملات الرياضية أو الخاصة بالمقارنة مثلا. على سبيل المثال:
a = int(input("Enter the first number: "))
b = int(input("Enter the second number: "))
print('The addition of',a,'and',b,'is :', a * b)

النتيجة:

Enter the first number: 12
Enter the second number: 10
The addition of 12 and 10 is : 120

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

  • الأخطاء الدلالية Semantic Errors : تحدث عندما نستعمل بشكل خاطئ دالة ما أو تعبيرا ما. مثال:
a = input("Enter a number: ")
print("The square root of",a,"is :", a * a )

في هذه الشفرة، لم نقم بتحويل نوع المتغير a من نص str إلى عدد صحيح int، لذا فسيظهر خطأ عند تشغيل البرنامج إذ لا يمكن إنجاز عمليات رياضية على النصوص:

ملاحظة: يُسمي البعض الأخطاء المنطقية بـ Semantic Errors، و يحدث هنا خلط بين كلا المفهومين، و ما يجب التركيز عليه هو فهم المغزى من كل نوع مهما اختلفت المُسمَّيات.

بايثون و الأخطاء:

في بايثون، تُقسم الأخطاء إلى نوعين: Syntax Errors و Exceptions:

  • Syntax errors : تظهر عند كتابة الكود بشكل خاطئ من ناحية قواعد اللغة.
  • Exceptions : تضم الأخطاء التي تحدث عند تشغيل شفرة برمجية ما.

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

مثال:

إذا حاولنا أن نُحول متغيرا ذا نوع نص، إلى متغير ذا نوع int أي عدد صحيح:

text = input('Please enter your name: ')
int(text)

سيظهر هذا الخطأ:

  • ValueError هو اسم الخطأ الذي تعرضنا له، من خلال الاسم نفهم أن الخطأ متعلق بالقيمة value.
  • invalid literal for int() with base 10 هو شرح المشكل، حيث يقول أننا أدخلنا قيمة لا تناسب النوع int()، و يعيد كتابة القيمة التي أدخلناها و هي Developer.
  • تُظهر لنا هذه الرسالة السطر الذي يتواجد فيه الخطأ line 2 و كذا مسار الملف المعني بالأمر (في هذه الحالة، اسم الملف هو exceptions.py )

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

إذا، كيف يمكن علاج الأمر؟

بايثون تُقدم لنا طريقة رائعة لمعالجة مثل هذه المشاكل، و ذلك باستخدام try و except :

… : try : … except

try هي وحدة أو مجموعة (block) تسمح لنا بتجربة شفرة معينة، و في حالة حدوث خطإ ما، يتم تشغيل الشفرة الموجودة في وحدة except أسفله.

يتم كتابتهما بالطريقة التالية:

try :
    #your code is written here
    #يتم كتابة الشفرة هنا
except:
    #the code that will run in case of an error
    #نكتب هنا شفرة أخرى لا تشتغل إلا في حالة وجود خطأ بالشفرة أعلاه

لنطبق الآن الأمر الآن على الشفرة التالية:

number = input("Enter a number: ")
number = int(number)

ستصبح الشفرة كالتالي:

number = input("Enter a number: ")
try:
    number = int(number)
except:
    print('You haven\'t entered a number')

داخل وحدة try نكتب الشفرة الخاصة بتحويل نوع المتغير من نص إلى عدد صحيح، و هذا هو السطر الذي يمكن أن يسبب لنا مشاكل في حالة إدخال قيم غير مناسبة.

و في وحدة except نكتب شفرة تظهر رسالة تشرح للمستخدم بأنه لم يُدخل عددا.

عند تشغيل الشفرة، حتى ولو أدخلنا نصا فلا يظهر لنا خطأ، بل تشتغل الشفرة بشكل طبيعي و تظهر لنا رسالة توضح للمستخدم أنه لم يدخل عددا.

ماذا لو كانت لدينا احتمالات أخطاء أكثر من واحدة؟

قد ننشئ شفرة ما تحمل احتمالات كثيرة لأخطاء مختلفة، و نرغب في تحديد رسالة معينة لكل خطأ.

مثال:

الشفرة التالية تطلب عددين من المستخدم و تحسب القسمة بينهما:

#Asking for values:
numerator = input("Please enter numerator: ")
denominator = input("Please enter the denominator: ")
#Changing their type to int():
numerator = int(numerator)
denominator = int(denominator)
#Calculating the division:
result = numerator/denominator
#Printing the result:
print('The result is :', result)

في حالة أدخلنا نصا عوض أعداد، يظهر الخطأ التالي:

  • اسم الخطأ: ValueError

في حالة أدخلنا 0 في المقام، أي الرقم الثاني، يظهر الخطأ التالي:

  • اسم الخطأ: ZeroDivisionError (القسمة على 0 غير ممكنة).

في حالة لم نغير نوع المتغيرين:

#Asking for values:
numerator = input("Please enter numerator: ")
denominator = input("Please enter the denominator: ")
#Calculating:
result = numerator/denominator
#Printing the result:
print('The result is ',result)

سيظهر الخطأ التالي:

  • اسم الخطأ TypeError : القسمة غير ممكنة بين النصوص.

كيف يمكننا إذا أن نعالج أكثر من خطأ؟

في هذه الحالة، سنستعمل اسم كل خطأ بالطريقة التالية:

#Asking for values:
numerator = input("Please enter numerator: ")
denominator = input("Please enter the denominator: ")
#Changing their type to int():
try:
    numerator = int(numerator)
    denominator = int(denominator)
except ValueError:
    print('You have not entered a number.')

#Calculating the division:
try:
    result = numerator/denominator
#Printing the result:
    print('The result is :', result)
except ZeroDivisionError:
    print('You cannot divide on 0.')
except TypeError:
    print('The type of the numerator or denominator is not compatible for the operation.')

إذا أدخلنا نصوصا، ستظهر الرسالة المرتبطة بالقيمة You have not entered a number، و ستظهر أيضا الرسالة الثانية المرتبطة بالخطأ TypeError (لأن العملية الرياضية لا يمكن أن تتحقق باستعمال نصوص).

أما إذا أدخلنا 0 في المقام، ستظهر الرسالة المتعلقة بهذا الخطأ، ZeroDivisionError .

ماذا لو أردنا أن نظهر رسالة الخطأ للمستخدم دون أن نكتبها بأنفسنا؟

في هذه الحالة، نقوم بتخزين الخطأ في متغير معين، نأخذ على سبيل المثال المتغير error_message:

#Asking for a value:
number = input('Enter a number: ')
#Calculating the square root and printing the result:
try:
    square_root = number ** 2
    print('The square root of',number,'is: ', b)
except TypeError as error_message:
    print('An error occured: ',error_message)

في حالة أدخلنا حرفا عوض رقم، تظهر الجملة التي كتبناها An error occured متبوعة بوصف الخطأ المُخَزَّن في المتغير error_message الذي يُوفره المترجم:

else ، finally و pass

else :

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

#Entering values:
number1 = input("Enter the first number: ")
number2 = input("Enter the second number: ")
#Changing types of values:
try :
    number1 = int(number1)
    number2 = int(number2)
except ValueError:
    print('Text values are not accepted.')
#Calculating:
try:
    number3 = number1/number2
except TypeError:
    print('The type of variables is not compatible for the operation.')
except ZeroDivisionError:
    print('Cannot devide on 0.')
else: #It will run only when no exception has occured
    print('The division of',number1,'on',number2,'is:',number3)
  • النتيجة في حالة حدوث خطأ:

لم يتم تطبيق الأمر الذي يوجد بداخل else لأنه تم حدوث خطأ.

  • النتيجة في حالة عدم حدوث خطأ:

تم تطبيق الأمر الذي يوجد بداخل else لأن البرنامج لم يتعرض لأية استثناءات.

finally :

تُستعمل هذه الكلمة المفتاحية بتطبيق أمر أو إظهار رسالة في كل الأحوال: أي سيتم تطبيق التعليمات بداخل هذه الكلمة سواء أتواجدت أخطاء أو لم تتواجد:

  • النتيجة في حالة حدوث خطأ:

يتم تطبيق الأمر داخل finally و لو تم حدوث خطأ.

  • النتيجة في حالة عدم حدوث خطأ:

يتم تطبيق الأمر داخل finally و لو لم تحدث أخطاء.

pass :

كما رأينا في درس سابق، فكلمة pass تسمح لنا بتجريب دالة ما أو مثلا في هذه الحالة، تجريب try و except ، دون كتابة أمر ما تحت الكلمة except:

a = "34E"
try :
    a = int(a)
except:
    pass

عند تشغيل هذه الشفرة، و بالرغم من وجود خطأ لأنه لا يمكننا أن نحول النص 34E إلى عدد صحيح، و لكن لن تظهر أية رسالة ذلك أننا لم نكتب شيئا داخل except، فكلمة pass تسمح لنا بتجريب الأمر دون إضافة أوامر أخرى.

رفع استثناء Raise an exception

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

مثلا: الشفرة التالية تطلب من المستخدم إدخال معدله الدراسي الذي يجب أن يكون بين 0 و 20 نقطة.

grade = input('Enter your grade (0-20) : ')
try:
    grade = int(grade)
    if grade < 0 or grade > 20:
        raise ValueError('Your grade must be between 0 and 20.')
except ValueError:
    print('Invalid Value')

في حالة إدخال المستخدم قيمة أصغر من 0 ، أو أكبر من 20، يحدث خطأ أو استثناء، و هو الذي قمنا بإنشائه من خلال السطرين:

if grade < 0 or grade > 20:
        raise ValueError('Your grade must be between 0 and 20.')

إذا، استعملنا الدالة الشرطية if من أجل تحديد ما إذا كان العدد المُدخل أكبر من 20 أو أصغر من 0، في حالة كان العدد فعلا إما سالبا أو يتعدى 20، سينشئ البرنامج الاستنثاء من خلال توظيف الكلمة المفتاحية raise، تليها نوع الخطأ، في هذه الحالة هو خطأ متعلق بالقيمة ValueError ، و بين القوسين نكتب رسالة الخطأ:

raise Error_type_نوع_الخطأ('Error message رسالة الخطأ')
  • عند تطبيق الشفرة في المثال السابق:

النتيجة عند إدخال رقم أكبر من 20:

Enter your grade (0-20) : 30
Invalid value.

تظهر الرسالة التي كانت بداخل الوحدة except.

  • عند تطبيق الشفرة التالية:
grade = input('Enter your grade (0-20) : ')
try:
    grade = int(grade)
    if grade < 0 or grade > 20:
        raise ValueError('Your grade must be between 0 and 20.')
except ValueError as msg:
    print(msg)

النتيجة عند إدخال رقم سالب:

Enter your grade (0-20) : -6
Your grade must be between 0 and 20.

تظهر هنا رسالة الخطأ المكتوبة بداخل قوسي ValueError عند إنشاء الاستثناء بواسطة raise.

Assert

الكلمة المفتاحية assert تسمح لنا بالتأكد من أن متغيرا ما يتحرم شرطا معين، في حالة عدم احترام المتغير للشرط، يظهر خطأ يسمى بـ AssertionError :

استعملنا الكلمة assert لنتأكد من أن المتغير a يساوي القيمة العددية 12 و لم يظهر أي خطأ. و لكن عندما حاولنا التأكد من مساواة a للقيمة العددية 13 ظهر خطأ : AssertionError ، مما يعني أن المتغير لا يساوي 13.

مثال:

grade = input('Enter your grade (0-20) : ')
try:
    grade = int(grade)
    assert grade >= 0 and grade <= 20
except AssertionError:
    print('You grade must be between 0 and 20.')

النتيجة عند إدخال الرقم 23:

Enter your grade (0-20) : 23
You grade must be between 0 and 20.

يمكن كتابة رسالة الخطأ في نفس سطر الكلمة assert كالتالي:

grade = input('Enter your grade (0-20) : ')
try:
    grade = int(grade)
    assert grade >= 0 and grade <= 20, 'Invalid Value.'
except AssertionError as msg:
    print(msg)

النتيجة عند إدخال الرقم 9- :

Enter your grade (0-20) : -9
Invalid Value.
السابق
7 : البرمجة بلغة بايثون – الوحدات Modules
التالي
خطوتك الأولى في تعلم البرمجة بلغة Java

شاركنا برأيك