5. استثناها
اکنون توجه خود را به استثناها معطوف میکنیم.

5.1. اسکریپت [exceptions_01]
اسکریپت اول نیاز به رسیدگی به استثناها را نشان میدهد.
ما عمداً یک خطا را ایجاد میکنیم تا ببینیم چه اتفاقی میافتد (exceptions-01.py):
خط ۴ به ما میدهد:
- نوع استثنا: ZeroDivisionError;
- پیام خطای مرتبط: تقسیم بر صفر. این پیام به زبان انگلیسی است. این چیزی است که ممکن است بخواهیم تغییر دهیم.
یک قاعده کلیدی این است که باید هر کاری که میتوانیم انجام دهیم تا از استثناءهایی که توسط تفسیرگر پایتون ایجاد میشوند جلوگیری کنیم. ما باید خطاها را خودمان مدیریت کنیم.
سینتکس مدیریت استثنا به شرح زیر است:
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** ادامه مییابد:
- خط ۳: اگر استثنای [ex] که رخ داده از نوعی باشد که به تپل (Ex1, Ex2…) تعلق دارد یا از یکی از آنها مشتق شده باشد، آنگاه اقدامات در خط ۴ اجرا میشوند؛
- خط ۵: اگر استثناء توسط خط ۳ مهار نشده باشد و یک بند [except] دیگر وجود داشته باشد، همان فرآیند تکرار میشود. و غیره…؛
- ممکن است به همان تعداد بند [except] که برای رسیدگی به انواع مختلف استثناهایی که ممکن است در [try] رخ دهند، لازم است، وجود داشته باشد؛
- اگر استثناء توسط هیچیک از بندهای [except] مدیریت نشده باشد، به کد فراخوانیکننده بازگردانده میشود. اگر خود کد فراخوانیکننده در داخل یک بلوک try/except باشد، استثناء دوباره مدیریت میشود؛ در غیر این صورت، به زنجیره متدهای فراخوانیشده بالاتر منتقل میشود. در آخرین مرحله، به تفسیرگر پایتون میرسد. سپس تفسیرگر برنامه در حال اجرا را متوقف کرده و پیغام خطایی از نوع نشان داده شده در مثال قبلی را نمایش میدهد. بنابراین، قانون این است که برنامه اصلی باید تمام استثناهایی را که ممکن است از متدهای فراخوانیشده منتقل شوند، بگیرد؛
- خط ۷: بند [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]
اسکریپت زیر بهطور صریح خطاها را مدیریت میکند:
# پردازش استثنا
essai = 0
# ایجاد خطا و رسیدگی به آن
x = 2
try:
x = 4 / 0
except ZeroDivisionError as erreur:
#خطا همان استثناء ضبطشده است
print(f"essai n° {essai} : {erreur}")
#مقدار x تغییر نکرده است
print(f"x={x}")
# ما دوباره شروع میکنیم
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# ما عمومیترین استثنا را میگیریم
#خطا استثناءی رهگیریشده است
print(f"essai n° {essai} : {erreur}")
# ما میتوانیم انواع مختلف استثناها را بگیریم
# اجرا در اولین [except] قادر به رسیدگی به استثنا متوقف میشود
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# این خطا در اینجا رخ نمیدهد
print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
# عمومیترین استثنا دریافت میشود
print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
# یک نوع خاص رهگیری میشود
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# ما با تغییر ترتیب دوباره تلاش میکنیم
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# این استثنا در اینجا رخ نمیدهد
print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
#یک نوع خاص گرفته میشود
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
# عمومیترین استثنا گرفته شده است
print(f"essai n° {essai} : (Exception) {erreur}")
#یک عبارت except بدون هیچ آرگومانی
essai += 1
try:
x = 4 / 0
except:
#ماهیت استثنا بیربط است
print(f"essai n° {essai} : il y a eu un problème")
# نوع دیگری از استثنا
essai += 1
try:
# x را نمیتوان به یک عدد صحیح تبدیل کرد
x = int("x")
except ValueError as erreur:
# 'error' استثناءی دریافتشده است
print(f"essai n° {essai} : {erreur}")
#یک استثنا اطلاعاتی را در یک تپل در دسترس برنامه حمل میکند
essai += 1
try:
x = int("x")
except ValueError as erreur:
# 'error' استثناء ضبطشده است
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# استثناها میتوانند پرتاب شوند
essai += 1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
#خطا همان استثنای دریافتشده است
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# شما میتوانید استثناءهای خود را ایجاد کنید
# آنها باید از کلاس [BaseException] مشتق شوند
class MyError(BaseException):
pass
# استثناء MyError پرتاب میشود
essai += 1
try:
raise MyError("info1", "info2", "info3")
except MyError as erreur:
#خطا همان استثنای رهگیریشده است
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
#استثناء MyError با یک پیام خطا پرتاب میشود
essai += 1
try:
raise MyError("mon msg d'erreur")
except MyError as erreur:
#خطا همان استثنای رهگیریشده است
print(f"essai n° {essai} : {erreur.args[0]}")
# بلوک finally همیشه اجرا میشود
# صرفنظر از اینکه استثناء رخ دهد یا خیر
essai += 1
x = None
try:
x = 1
except:
# استثناء
print(f"essai n° {essai} : exception")
finally:
#در همه موارد اجرا میشود
print(f"essai n° {essai} : finally x={x}")
essai += 1
x = None
try:
x = 2 / 0
except:
# استثناء
print(f"essai n° {essai} : exception")
finally:
# در همه موارد اجرا میشود
print(f"essai n° {essai} : finally x={x}")
# لازم نیست یک بند [except] را درج کرد
essai += 1
try:
# این باعث ایجاد خطا میشود
x = 4 / 0
finally:
#در همه موارد اجرا شد
print(f"essai n° {essai} : finally x={x}")
یادداشتها:
- خطوط ۴–۱۲: تقسیم بر صفر مدیریت میشود؛
- خط ۸: استثنای دقیقی که رخ میدهد گرفته شده و نمایش داده میشود؛
- خط ۱۲: به دلیل استثنایی که رخ داده است، x در خط ۷ مقداری دریافت نکرده و در نتیجه مقدار آن تغییر نکرده است؛
- خطوط 14–21: ما همان فرآیند را تکرار میکنیم اما یک استثنای سطح بالاتر از نوع BaseException را میگیریم. از آنجایی که استثنای ZeroDivisionError از کلاس BaseException مشتق شده است، بند except آن را خواهد گرفت؛
- خطوط ۲۳–۳۶: چندین عبارت except برای رسیدگی به انواع استثناءهای متعدد گنجانده شده است. تنها یک عبارت except اجرا خواهد شد، یا اگر استثناء با هیچیک از عبارتهای «except» مطابقت نداشته باشد، هیچکدام اجرا نخواهند شد؛
- خطوط ۳۸–۵۰: ما همین فرآیند را تکرار میکنیم و با تغییر ترتیب بندهای [except]، نقش آنها را نشان میدهیم؛
- خطوط ۵۲–۵۸: بند except ممکن است هیچ آرگومانتی نداشته باشد. در این صورت، تمام استثناها را خاتمه میدهد؛
- خطوط ۶۰–۶۷: استثنای ValueError را معرفی میکنیم؛
- خطوط ۶۹–۷۵: اطلاعاتی که توسط استثنا حمل میشود، بازیابی میشود؛
- خطوط ۷۷–۸۳: نحوهٔ ایجاد یک استثنا را معرفی میکند؛
- خطوط ۸۶–۱۰۶: کاربرد یک کلاس استثنای سفارشی، MyError، را نشان میدهد. کلاس MyError صرفاً از کلاس پایه BaseException ارث میبرد. این کلاس هیچ چیزی به کلاس پایه خود اضافه نمیکند. با این حال، اکنون میتوان آن را به صراحت در عبارتهای «except» نام برد؛
- خطوط ۱۰۸–۱۳۰: کاربرد عبارت finally را نشان میدهند؛
- خطوط ۱۳۲–۱۳۹: این خطوط نشان میدهند که بند [except] اجباری نیست؛
خروجی صفحه به شرح زیر است:
5.3. اسکریپت [exceptions_03]
این اسکریپت جدید نشان میدهد که استثناها چگونه در زنجیره فراخوانی به سمت بالا منتقل میشوند:
#یک استثنای سفارشی
class MyError(BaseException):
pass
#سه تابع
def f1(x: int) -> int:
#استثناءها مدیریت نمیشوند – آنها بهطور خودکار منتقل میشوند
return f2(x)
def f2(y: int) -> int:
# استثناها رسیدگی نمیشوند – آنها بهطور خودکار منتقل میشوند
return f3(y)
def f3(z: int) -> int:
if (z % 2) == 0:
# اگر z زوج باشد، یک استثنا پرتاب میشود
raise MyError("exception dans f3")
else:
return 2 * z
# ---------- main
# استثناها در زنجیره متدهای فراخوانیشده به سمت بالا منتقل میشوند
# تا زمانی که یک متد آن را بگیرد. در این صورت، `main` خواهد بود
try:
print(f1(4))
except MyError as erreur:
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# سه تابع دیگر که استثناءهایی را که منتقل میکنند غنیسازی میکنند
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# استثناء غنیسازی شده و سپس دوباره پرتاب میشود
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# استثناء را غنیسازی کرده و سپس دوباره پرتاب میکنیم
raise MyError("exception dans f5", erreur)
def f6(z: int) -> int:
if (z % 2) == 0:
# اگر z زوج باشد، یک استثنا پرتاب میشود
raise MyError("exception dans f6")
else:
return 2 * z
# ---------- main
try:
print(f4(4))
except MyError as erreur:
# استثناء را نمایش دهید
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# میتوانید پشته استثناء را ردیابی کنید
err = erreur
# پیام خطا نمایش داده میشود
print(err.args[0])
#آیا یک استثنا در بر گرفته شده است؟
while len(err.args) == 2 and isinstance(err.args[1], BaseException):
# تغییر استثنا
err = err.args[1]
# اولین آرگومان پیام خطا است
print(err.args[0])
یادداشتها:
- خطوط ۲۵–۳۲: در فراخوانی main → f1 → f2 → f3 (خط ۳۰)، استثنای MyError که توسط f3 پرتاب شده است، تا main بالا میرود. سپس توسط بند except در خط ۳۱ رسیدگی خواهد شد؛
- خطوط ۶۱–۷۵: در فراخوانی main → f4 → f5 → f6 (خط ۶۲)، استثنای MyError که توسط f6 پرتاب میشود، تا main بالا خواهد رفت. سپس توسط بند except در خط 63 رسیدگی خواهد شد. این بار، هنگامی که در زنجیره تابعهای فراخوانی صعود میکند، استثنای MyError که به سمت بالا منتقل میشود، خود در درون یک استثنای دیگر قرار میگیرد؛
- خطوط ۶۶–۷۵: نشان میدهند چگونه در پشته استثناها پیمایش کنیم؛
- خط ۷۱: تابع [isinstance(instance, Classe)] مقدار True را برمیگرداند اگر شیء [instance] از نوع [Classe] یا یکی از انواع مشتقشده باشد. در اینجا از استثنای سطح بالا [BaseException] استفاده کردهایم که تضمین میکند تمام استثناها را میگیریم؛
خروجی صفحه به شرح زیر است: