مدیریت خطا و استثناها
در این درس تفاوت خطای نحو و استثنا را میفهمید، با try و except خطاها را مدیریت میکنید، پیام خطا را میخوانید و با raise خطای معنادار ایجاد میکنید.
آنچه در این درس میآموزید
- خطای نحو را از استثنای زمان اجرا جدا کنید
- با try و except استثنای مشخصی را بگیرید و برنامه را زنده نگه دارید
- با raise در تابع خودتان خطای معنادار ایجاد کنید
فهرست مطالب
خطا دشمن نیست؛ پیام است. تا اینجا هر خطایی برنامه را همانجا متوقف میکرد و شما آن را خوانده و کد را اصلاح میکردید. اما برنامهای که تحویل کاربر میدهید نمیتواند هر ورودی خرابی را با توقف جواب دهد: فایلی ممکن است وجود نداشته باشد، کاربر بهجای عدد حرف تایپ کند، شبکه قطع شود. مدیریت خطا یعنی پیشبینی این لحظهها و برنامهریزی برای برخورد باوقار با آنها.
پایتون دو نوع خطا با سرشت متفاوت دارد و در این درس هر دو را میشناسید و ابزار مدیریت استثنا را کامل میبینید.
خطای نحو و استثنا
نوع اول خطای نحو (Syntax Error) است؛ یعنی جملهٔ کد از قواعد زبان پیروی نمیکند:
print("hello"SyntaxError: '(' was never closed
پایتون خطای نحو را قبل از اجرا میگیرد؛ برنامه اصلاً شروع نمیشود. نوع دوم استثنا (Exception) است: کد از نظر نحو درست است، ولی در میانهٔ اجرا به مانعی میخورد؛ تقسیم بر صفر، اندیس ناموجود، فایل باز نشده. چند استثنای پرتکرار:
| استثنا | چه وقت رخ میدهد |
|---|---|
| NameError | استفاده از نامی که تعریف نشده |
| TypeError | عملیات روی نوع نامناسب، مثل جمع رشته با عدد |
| ValueError | نوع درست اما مقدار نامناسب، مثل int("سلام") |
| IndexError | اندیس بیرون از محدودهٔ لیست |
| KeyError | کلید ناموجود در دیکشنری |
| ZeroDivisionError | تقسیم بر صفر |
| FileNotFoundError | باز کردن فایلی که وجود ندارد |
استثنا بهطور پیشفرض برنامه را قطع میکند؛ مگر اینکه خودتان بگویید با آن چه کند. ابزار این کار try و except است.
گرفتن استثنا با try و except
بلوک try کدِ مستعد خطا را میآزماید؛ اگر استثنای مشخصی رخ دهد، کنترل به بلوک except میپرد و برنامه به راهش ادامه میدهد:
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
print(safe_divide(10, 2))
print(safe_divide(10, 0))5.0 None
فراخوانی دوم در دنیای بدون try به اجرا ختم نمیشد؛ اینجا تابع بهجای فروپاشی، None برمیگرداند و برنامه سطر بعد را هم اجرا میکند. توجه کنید فقط همان استثنایی را گرفتیم که انتظارش را داشتیم؛ این انتخابگری کلید درس است.
خواندن پیام خطا
با عبارت as میتوانید خودِ شیء خطا را بگیرید و پیامش را بخوانید یا ثبت کنید:
def to_int(text):
try:
return int(text)
except ValueError as error:
print(f"Invalid input: {error}")
return None
print(to_int("42"))
print(to_int("hello"))42 Invalid input: invalid literal for int() with base 10: 'hello' None
پیام خطا توضیح دقیق میدهد: تابع int نمیتواند رشتهٔ «سلام» را به عدد تبدیل کند. خواندن پیام خطا مهارت است؛ نصف رفع اشکال همین است که آن را واقعاً بخوانید.
else و finally
ساختار کامل try دو بخش اختیاری دیگر هم دارد:
try:
value = int("7")
except ValueError:
print("Conversion failed")
else:
print(f"Value: {value}")
finally:
print("Done")Value: 7 Done
بلوک else فقط وقتی اجرا میشود که هیچ خطایی رخ نداده باشد؛ جای منطق موفقیت. بلوک finally در هر دو حالت اجرا میشود؛ جای پاککاری مثل بستن فایل و آزاد کردن منابع.
ایجاد خطا با raise
توابع شما هم میتوانند قراردادهایشان را با خطای معنادار اعمال کنند؛ به این کار «مطرح کردن» استثنا میگویند:
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
try:
set_age(-5)
except ValueError as error:
print(error)Age cannot be negative
بهجای برگرداندن عدد منفی و منتقلکردن مسئله به پاییندست، تابع همانجا اعتراض میکند و پیامش روشن است. خطای واضح و زودهنگام، بهتر از دادهٔ خرابِ پنهان است.
تمرینها
تمرین
تمرین ۱ — خواندن امن از لیست
تابعی به نام get_item بنویسید که یک لیست و یک اندیس بگیرد و عضو آن جایگاه را برگرداند؛ اگر اندیس بیرون از محدوده بود، بهجای خطا رشتهٔ «اندیس خارج از محدوده» را برگرداند و برنامه به کار ادامه دهد.
# Write your code hereنمایش راهحل
def get_item(items, index):
try:
return items[index]
except IndexError:
return "Index out of range"
print(get_item([10, 20, 30], 1))
print(get_item([10, 20, 30], 7))20 Index out of range
::: :::
خلاصه
- خطای نحو قبل از اجرا گرفته میشود؛ استثنا در میانهٔ اجرا رخ میدهد و پیشفرض برنامه را قطع میکند.
- هر استثنا نام مشخص دارد: ValueError، IndexError، KeyError و چند مورد دیگر.
tryوexceptاستثنای مشخصی را میگیرند و برنامه را زنده نگه میدارند؛ باasپیام خطا در دسترس است.elseبرای موفقیت است وfinallyهمیشه اجرا میشود؛ جای پاککاری.- با
raiseدر تابع خودتان خطای معنادار ایجاد کنید و هرگزexceptلخت ننویسید.
حالا که خطاها را مهار میکنید، آمادهٔ خطرناکترین کار روزمرهاید: کار با فایلها؛ جایی که فایل ممکن است نباشد و باید بدانید چه کنید. در درس بعدی مینویسیم، میخوانیم و پاک میکنیم.