5. الاستثناءات
سننتقل الآن إلى الاستثناءات.

5.1. نص برمجي [exceptions_01]
يوضح البرنامج النصي الأول الحاجة إلى معالجة الاستثناءات.
| # on provoque volontairement une erreur
x = 4 / 0
|
نقوم بإحداث خطأ عن قصد لنرى ما سيحدث (exceptions-01.py):
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
x = 4 / 0
ZeroDivisionError: division by zero
Process finished with exit code 1
|
السطر 4 يعطينا:
- نوع الاستثناء: ZeroDivisionError؛
- رسالة الخطأ المرتبطة: القسمة على صفر. وهي باللغة الإنجليزية. قد ترغب في تغيير هذا.
إحدى القواعد الأساسية هي أننا يجب أن نبذل قصارى جهدنا لتجنب الاستثناءات التي يولدها مترجم Python. علينا معالجة الأخطاء بأنفسنا.
صيغة معالجة الاستثناءات هي كما يلي:
try:
actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
actions de gestion de l'exception [ex]
finally:
actions toujours exécutées qu'il y ait exception ou non
في كتلة try، يتوقف تنفيذ الإجراءات بمجرد حدوث استثناء (خطأ). في هذه الحالة، يستمر التنفيذ بإجراءات إحدى جمل except:
- السطر 3: إذا كان الاستثناء [ex] الذي حدث من نوع ينتمي إلى التوبلة (Ex1, Ex2…) أو مشتقًا من أحدها، يتم تنفيذ الإجراءات الموجودة في السطر 4؛
- السطر 5: إذا لم يتم التقاط الاستثناء بواسطة السطر 3، وكان هناك جملة [except] أخرى، فسيحدث نفس الأمر. وهكذا...؛
- يمكن أن يكون هناك عدد من جمل [except] حسب الضرورة للتعامل مع الأنواع المختلفة من الاستثناءات التي قد تحدث داخل كتلة [try]؛
- إذا لم يتم التعامل مع الاستثناء بواسطة أي من جمل [except]، فسيتم تمريره مرة أخرى إلى الكود المستدعي. إذا كان الكود المستدعي نفسه داخل بنية try/except، يتم التعامل مع الاستثناء مرة أخرى؛ وإلا، يستمر في الانتشار عبر سلسلة الطرق المستدعاة. في النهاية، يصل إلى مترجم Python. يقوم المترجم بعد ذلك بإنهاء البرنامج قيد التشغيل وعرض رسالة خطأ من النوع الموضح في المثال السابق. لذلك، فإن القاعدة هي أن البرنامج الرئيسي يجب أن يلتقط جميع الاستثناءات التي قد تنتقل من الطرق المستدعاة؛
- السطر 7: يتم دائمًا تنفيذ جملة [finally]، سواء حدث استثناء (بعد except) أم لا (بعد try). وينطبق هذا حتى إذا حدث استثناء ولم يتم التقاطه. في هذه الحالة، سيتم تنفيذ جملة [finally] قبل أن ينتقل الاستثناء مرة أخرى إلى الكود المستدعي؛
تحمل الاستثناءات معلومات حول الخطأ الذي حدث. يمكن الحصول على هذه المعلومات باستخدام الصيغة التالية:
except MyException as exception:
[exception] هو الاستثناء الذي حدث. [exception.args] يمثل التوبلة الخاصة بمعلمات الاستثناء.
لإلقاء استثناء، استخدم الصيغة
raise MyException(param1, param2…)
حيث تكون MyException في الغالب فئة مشتقة من فئة BaseException. ستكون المعلمات التي تم تمريرها إلى منشئ الفئة متاحة لعبارة except في هياكل معالجة الاستثناءات باستخدام الصيغة [ex.args] إذا كانت [ex] هي الاستثناء الذي تم التقاطه بواسطة عبارة [except].
يتم توضيح هذه المفاهيم من خلال البرنامج النصي التالي.
5.2. النص البرمجي [exceptions_02]
يعالج البرنامج النصي التالي الأخطاء بشكل صريح:
| # exception handling
essai = 0
# cause an error and manage it
x = 2
try:
x = 4 / 0
except ZeroDivisionError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# the value of x has not changed
print(f"x={x}")
# here we go again
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# we intercept the most general exception
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# different types of exceptions can be intercepted
# execution stops on the 1st [except] able to handle the exception
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# this exception does not occur here
print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
# we intercept the most general exception
print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
# we intercept a specific type
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# start again, changing the order
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# this exception does not occur here
print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
# we intercept a specific type
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
# we intercept the most general exception
print(f"essai n° {essai} : (Exception) {erreur}")
# an except clause with no arguments
essai += 1
try:
x = 4 / 0
except:
# we're not interested in the nature of the exception
print(f"essai n° {essai} : il y a eu un problème")
# another type of exception
essai += 1
try:
# x cannot be converted to an integer
x = int("x")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# an exception transports information in a tuple accessible to the program
essai += 1
try:
x = int("x")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# exceptions can be thrown
essai += 1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# you can create your own exceptions
# they must derive from class [BaseException]
class MyError(BaseException):
pass
# throw MyError exception
essai += 1
try:
raise MyError("info1", "info2", "info3")
except MyError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# throw MyError exception with error msg
essai += 1
try:
raise MyError("mon msg d'erreur")
except MyError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur.args[0]}")
# the finally clause is always executed
# whether or not there is an exception
essai += 1
x = None
try:
x = 1
except:
# exception
print(f"essai n° {essai} : exception")
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
essai += 1
x = None
try:
x = 2 / 0
except:
# exception
print(f"essai n° {essai} : exception")
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
# we don't have to include an except clause
essai += 1
try:
# we cause an error
x = 4 / 0
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
|
ملاحظات:
- الأسطر 4–12: التعامل مع القسمة على الصفر؛
- السطر 8: نلتقط الاستثناء الذي حدث بالضبط ونعرضه؛
- السطر 12: بسبب الاستثناء الذي حدث، لم يتلق x قيمة في السطر 7 وبالتالي لم يتغير قيمته؛
- الأسطر 14–21: نقوم بنفس الشيء ولكننا نلتقط استثناءً أعلى مستوى من النوع BaseException. نظرًا لأن استثناء ZeroDivisionError هو فئة فرعية من BaseException، فإن جملة except ستلتقطه؛
- الأسطر 23–36: نستخدم عدة جمل except لمعالجة أنواع متعددة من الاستثناءات. سيتم تنفيذ جملة except واحدة فقط، أو لن يتم تنفيذ أي منها على الإطلاق إذا لم يتطابق الاستثناء مع أي جملة except؛
- الأسطر 38–50: نقوم بنفس الشيء، مع تغيير ترتيب جمل [except] لإبراز دورها؛
- الأسطر 52–58: قد لا تحتوي جملة `except` على أي حجج. في هذه الحالة، تلتقط جميع الاستثناءات؛
- الأسطر 60–67: نقدم استثناء ValueError؛
- الأسطر 69–75: نسترد المعلومات التي يحملها الاستثناء؛
- الأسطر 77–83: نقدم كيفية إثارة استثناء؛
- الأسطر 86–106: توضيح استخدام فئة استثناء مخصصة، MyError. فئة MyError ترث ببساطة من الفئة الأساسية BaseException. وهي لا تضيف شيئًا إلى فئتها الأساسية. ومع ذلك، يمكن الآن تسميتها صراحةً في جمل except؛
- الأسطر 108–130: توضح استخدام جملة finally؛
- الأسطر 132–139: توضح هذه الأسطر أن جملة [except] ليست إلزامية؛
إخراج الشاشة كما يلي:
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None
Process finished with exit code 1
|
5.3. نص برمجي [exceptions_03]
يوضح هذا البرنامج النصي الجديد كيفية انتقال الاستثناءات عبر سلسلة وظائف الاستدعاء:
| # a proprietary exception
class MyError(BaseException):
pass
# three functions
def f1(x: int) -> int:
# exceptions are not handled - they are automatically raised
return f2(x)
def f2(y: int) -> int:
# exceptions are not handled - they are automatically raised
return f3(y)
def f3(z: int) -> int:
if (z % 2) == 0:
# if z is even, throw an exception
raise MyError("exception dans f3")
else:
return 2 * z
# ---------- hand
# exceptions go up the chain of methods called
# until a method intercepts it. In this case, it will be main
try:
print(f1(4))
except MyError as erreur:
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# three other functions that enrich the exceptions it tracks
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# we enrich the exception then relaunch it
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# we enrich the exception then relaunch it
raise MyError("exception dans f5", erreur)
def f6(z: int) -> int:
if (z % 2) == 0:
# throw an exception if z is even
raise MyError("exception dans f6")
else:
return 2 * z
# ---------- hand
try:
print(f4(4))
except MyError as erreur:
# exception display
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# you can move up the exception stack
err = erreur
# error msg is displayed
print(err.args[0])
# is an exception encapsulated?
while len(err.args) == 2 and isinstance(err.args[1], BaseException):
# exception change
err = err.args[1]
# the 1st argument is the error msg
print(err.args[0])
|
ملاحظات:
- الأسطر 25–32: في الاستدعاء main → f1 → f2 → f3 (السطر 30)، ستنتقل استثناء MyError الذي أثاره f3 إلى main. ثم سيتم التعامل معه بواسطة جملة except في السطر 31؛
- الأسطر 61–75: في الاستدعاء main → f4 → f5 → f6 (السطر 62)، سينتقل استثناء MyError الذي أثاره f6 إلى main. ثم سيتم التعامل معه بواسطة جملة except في السطر 63. هذه المرة، أثناء انتقاله عبر سلسلة وظائف الاستدعاء، فإن استثناء MyError الذي ينتقل يكون مغلفًا بحد ذاته داخل استثناء آخر؛
- الأسطر 66–75: توضح كيفية تتبع مكدس الاستثناءات؛
- السطر 71: تُرجع الدالة [isinstance(instance, Class)] القيمة True إذا كان الكائن [instance] من النوع [Class] أو فئة فرعية. هنا، استخدمنا الاستثناء الأعلى مستوى [BaseException]، الذي يضمن التقاط جميع الاستثناءات؛
إخراج الشاشة كما يلي:
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6
Process finished with exit code 0
|