خطا و استثناها در پایتون
آیا تا به حال در حال اجرای یک برنامه پایتون بودهاید که ناگهان متوقف شود و با یک پیام خطای ترسناک مواجه شوید؟ این اتفاق (که در اصطلاح فنی به آن کرش کردن میگویند) نه تنها برای توسعهدهنده بلکه برای کاربر نهایی نیز تجربهای ناخوشایند است.
در دنیای برنامهنویسی واقعی، مدیریت دقیق و هوشمندانه خطاها، تفاوت بین یک برنامه آماتور و یک سیستم نرمافزاری پایدار و قابل اعتماد است.
در این درس جامع، شما به یک مهارت حیاتی در پایتون مسلط خواهید شد: مدیریت خطا در پایتون و رسیدگی به استثناها (Exception Handling) با استفاده از ساختار قدرتمند try except در پایتون.
ما ابتدا تفاوت کلیدی بین خطاهای سینتکس (Syntax Errors) و استثناها (Exceptions) را روشن میکنیم. سپس، به طور عمیق یاد میگیرید که چطور از بلوکهای try، except، else و finally استفاده کنید تا بتوانید انواع خطاها در پایتون (مانند ZeroDivisionError، ValueError، یا FileNotFoundError) را به درستی تشخیص دهید و کنترل کنید.
هدف این آموزش این است که شما نه تنها بدانید چگونه از try except استفاده کنید، بلکه بدانید چه زمانی و چگونه آن را به شکلی بنویسید که کد شما خواناتر، امنتر و حرفهایتر باشد.
با اتمام این درس، شما قادر خواهید بود برنامههایی بنویسید که در برابر ورودیهای نامعتبر یا مشکلات محیطی، ضدضربه باشند..
فهرست عناوین
۱. تفاوت بنیادین: خطاها و استثناها در پایتون
در مسیر یادگیری مدیریت خطا در پایتون، اولین گام درک این است که تمام "مشکلات" یکسان نیستند. پایتون دو دستهبندی اصلی برای مشکلات برنامه دارد: خطاهای سینتکس (Syntax Errors) و استثناها (Exceptions). درک این تفاوت، کلید پیادهسازی صحیح try except است.
۱.۱. خطای سینتکس (Syntax Error): چرا برنامه شما هرگز شروع نمیشود؟
خطای سینتکس یا خطای نحوی زمانی رخ میدهد که شما قوانین نوشتاری زبان پایتون را نقض کرده باشید. مفسر پایتون حتی نمیتواند کد شما را "بخواند" تا آن را اجرا کند. این نوع خطاها معمولاً قبل از شروع اجرای برنامه تشخیص داده میشوند و باعث میشوند برنامه شما اصلاً اجرا نشود.
-
کلمات کلیدی پوشش داده شده: خطای سینتکس در پایتون، ارور در پایتون.
-
مثال رایج: فراموش کردن علامت دو نقطه (:) بعد از دستور if یا پرانتز بسته.
مثال کد (که باعث خطای سینتکس میشود):
if 5 > 2
print("5 بزرگتر است")
پایتون قبل از اجرا، با پیام خطایی شبیه به SyntaxError: expected ':' مواجه میشود.
۱.۲. استثنا (Exception): وقتی خطا در زمان اجرا رخ میدهد
استثناها، که در این درس با ساختار try except در پایتون به آنها رسیدگی میکنیم، زمانی رخ میدهند که نحو (Syntax) کد شما درست است، اما در زمان اجرای برنامه، به دلیل یک مشکل منطقی یا شرایط غیرمنتظره، مفسر قادر به ادامه نیست.
استثناها، بر خلاف خطاهای سینتکس، قابل کنترل و مدیریت هستند. اینجاست که بلوکهای try و except وارد عمل میشوند.
-
کلمات کلیدی پوشش داده شده: استثنا در پایتون، Exception Handling، تفاوت exception و syntax error.
-
مثال رایج: تقسیم یک عدد بر صفر، تلاش برای باز کردن یک فایل ناموجود، یا تبدیل یک متن به عدد.
مثال کد (که باعث استثنا میشود):
عدد_یک = 10
عدد_دو = 0
نتیجه = عدد_یک / عدد_دو # در اینجا استثنا رخ میدهد
در این حالت، کد شما از نظر نوشتاری درست است، اما در خط سوم یک استثنا از نوع ZeroDivisionError رخ میدهد که برنامه را متوقف میکند، مگر اینکه توسط مدیریت خطا در پایتون کنترل شده باشد.
نکته کلیدی برای برنامهنویسان
تفاوت کلیدی: شما نمیتوانید یک خطای سینتکس را با try...except مدیریت کنید؛ باید آن را اصلاح کنید. اما استثناها را میتوان و باید با استفاده از رسیدگی به استثناها (try...except) کنترل کرد تا برنامه در مقابل ورودیهای غیرمنتظره مقاومت کند.
۲. بلوک try except در پایتون: هسته مدیریت خطا
همانطور که یاد گرفتیم، استثناها (Exceptions) مشکلات قابل کنترلی هستند که در زمان اجرای برنامه رخ میدهند. برای رسیدگی به این مشکلات و جلوگیری از توقف ناگهانی برنامه (Crashing)، پایتون ابزار اصلی خود را ارائه میدهد: ساختار try except در پایتون. این بلوک، ستون فقرات Exception Handling در هر برنامه حرفهای پایتون است.
۲.۱. ساختار پایه: نحوه استفاده از try except در پایتون (H3)
بلوک try except به پایتون میگوید: "سعی کن (Try) این کار را انجام دهی، اگر موفق نشدی و یک استثنا رخ داد، به جای متوقف شدن، این کار جایگزین (Except) را انجام بده."
-
کلمات کلیدی پوشش داده شده: آموزش try except پایتون، نحوه استفاده از try except در پایتون، بلوک try در پایتون.
نحوه عملکرد:
-
try: کدی که احتمال میدهید در آن خطا رخ دهد، در این بلوک قرار میگیرد. اگر خطایی رخ ندهد، بلوک except کاملاً نادیده گرفته میشود.
-
except: اگر در بلوک try یک استثنا رخ دهد، اجرای کد بلافاصله متوقف شده و کنترل به بلوک except منتقل میشود. کدهای داخل except (که معمولاً شامل اطلاعرسانی به کاربر یا ثبت خطا هستند) اجرا میشوند.
مثال کد پایه:
فرض کنید میخواهیم یک عدد را از کاربر بگیریم و آن را به یک عدد صحیح (Integer) تبدیل کنیم. اگر کاربر به جای عدد، متن وارد کند، خطای ValueError رخ میدهد.
try:
# کد ریسکپذیر: سعی کن ورودی را به عدد تبدیل کنی
ورودی_کاربر = input("لطفاً یک عدد وارد کنید: ")
عدد_صحیح = int(ورودی_کاربر)
print(f"شما عدد {عدد_صحیح} را وارد کردید.")
except:
# کد جایگزین: در صورت بروز هر نوع خطا، این پیغام نمایش داده شود
print("خطا! ورودی شما یک عدد معتبر نبود. لطفاً دوباره تلاش کنید.")
نکته: در مثال بالا، استفاده از یک except خالی، هر نوع خطایی را مدیریت میکند. گرچه برای سادگی اولیه خوب است، اما در کدنویسی حرفهای توصیه میشود که همیشه نوع خطایی را که انتظار مدیریت آن را دارید، صراحتاً مشخص کنید.
۲.۲. مدیریت انواع خطاها در پایتون با exceptهای چندگانه (H3)
همانطور که اشاره شد، بهتر است به پایتون بگوییم دقیقاً منتظر چه نوع استثنایی هستیم. این کار باعث میشود اگر یک خطای پیشبینی نشده (مانند یک NameError که نشاندهنده یک اشکال واقعی در کدنویسی است) رخ دهد، برنامه متوقف شود و اشکال را به شما نشان دهد، نه اینکه آن را پنهان کند.
-
کلمات کلیدی پوشش داده شده: مدیریت انواع خطاها در پایتون، ValueError در پایتون.
مثال کد با تعیین نوع خطا:
در اینجا ما به طور خاص خطای ValueError را که هنگام تبدیل رشته به عدد نامعتبر رخ میدهد، هدف قرار میدهیم:
try:
# تبدیل ورودی کاربر (ریسکپذیر)
ورودی = input("یک عدد وارد کنید: ")
نتیجه_تقسیم = 10 / int(ورودی)
print(f"نتیجه تقسیم: {نتیجه_تقسیم}")
except ValueError:
# فقط در صورت بروز خطای تبدیل (ValueError) اجرا میشود
print("خطای ورودی: شما یک عدد صحیح وارد نکردید.")
except ZeroDivisionError:
# فقط در صورت تقسیم بر صفر اجرا میشود
print("خطای ریاضی: تقسیم بر صفر مجاز نیست.")
except Exception as e:
# برای مدیریت هر نوع خطای دیگری که پیشبینی نشده است
print(f"یک خطای نامشخص رخ داد: {e}")
نتیجه: استفاده از چندین بلوک except به شما امکان میدهد برای هر نوع استثنا، یک روش رسیدگی منحصربهفرد و معنیدار را پیادهسازی کنید. این یک اصل مهم در مدیریت خطا در پایتون برای ساختن برنامههای پایدار است.
۳. رسیدگی تخصصی به استثناها: تشخیص و ثبت خطاها
برنامهنویسی حرفهای به معنای فقط "گرفتن خطا" نیست، بلکه به معنای "دانستن اینکه چه خطایی رخ داده و چرا" است. در این بخش، یاد میگیرید چطور با استفاده از تکنیکهای پیشرفتهتر، جزئیات استثناها را ثبت و مدیریت کنید تا در نهایت یک برنامه ضدضربه و قابل اشکالزدایی (Debugging) بسازید.
۳.۱. استفاده از as برای دسترسی به جزئیات خطا (Tracing) (H3)
برای اینکه بفهمید دقیقاً چه اتفاقی افتاده، میتوانید استثنای ایجاد شده را در یک متغیر ذخیره کنید. با استفاده از کلمه کلیدی as میتوانید شیء (Object) استثنا را بگیرید و پیغام خطای آن را چاپ یا در یک فایل ثبت کنید.
-
کلمات کلیدی پوشش داده شده: رسیدگی به استثناها در پایتون، ثبت خطا در پایتون.
مثال کد:
try:
نتیجه = 10 / 0
except ZeroDivisionError as e:
# "e" حاوی جزئیات کامل استثنای رخ داده است
print("خطا! یک تقسیم بر صفر رخ داد.")
# چاپ پیغام فنی خطا برای ثبت یا اشکالزدایی
print(f"پیغام سیستم: {e}")
نکته: e در اینجا یک نام قراردادی است، اما معمولاً در جامعه پایتون برای اشاره به شیء استثنا (Exception Object) استفاده میشود. استفاده از جزئیات فنی خطا (مثل e) برای برنامهنویسان ضروری است، اما به کاربر نهایی نباید نمایش داده شود.
۳.۲. مدیریت استثناهای رایج: از تقسیم بر صفر تا عملیات فایل (H3)
برای بهبود سئو، مقاله شما باید به سؤالات واقعی کاربران در مورد خطاهایی که مرتب با آنها روبرو میشوند پاسخ دهد. استفاده از بلوک try except برای مدیریت این انواع خطاها در پایتون حیاتی است.
-
کلمات کلیدی پوشش داده شده: ZeroDivisionError در پایتون، ValueError در پایتون، FileNotFoundError در پایتون.
الف) خطای ValueError (خطای مقدار)
این خطا معمولاً زمانی رخ میدهد که نوع داده درست است (مثلاً یک رشته)، اما مقدار آن برای عملیات مورد نظر مناسب نیست (مثلاً تبدیل رشته "hello" به عدد صحیح).
try:
عدد = int(input("عدد مورد نظر را وارد کنید: "))
except ValueError:
print("هشدار: لطفاً ورودی را به شکل یک عدد معتبر وارد نمایید.")
ب) خطای FileNotFoundError (خطای فایل)
این یکی از رایجترین خطاهایی است که هنگام کار با فایلها رخ میدهد. try except تضمین میکند که برنامه شما حتی اگر فایلی پیدا نشود، متوقف نمیگردد.
try:
# سعی میکنیم یک فایل ناموجود را باز کنیم
با باز کردن("فایل_ناموجود.txt", "r") به عنوان f:
محتوا = f.read()
except FileNotFoundError:
print("خطا: فایل مورد نظر برای خواندن، پیدا نشد.")
# در اینجا میتوانید به جای توقف، یک فایل جدید ایجاد کنید یا مسیر را دوباره بپرسید
۳.۳. گروه بندی خطاها: رسیدگی به چندین استثنا به صورت همزمان (H3)
اگر بخواهید برای چند نوع خطای مختلف، یک نوع رسیدگی مشابه را انجام دهید، میتوانید آنها را در یک پرانتز کنار هم بیاورید. این کار باعث میشود کد شما کوتاهتر و خواناتر شود.
مثال کد:
try:
# کدی که ممکن است ZeroDivisionError یا TypeError بدهد
result = some_function_call()
except (ZeroDivisionError, TypeError):
# این بلوک در صورت وقوع هر یک از دو خطای بالا اجرا میشود
print("عملیات با مشکل ریاضی یا نوع داده مواجه شد.")
۴. ساختارهای پیشرفته: else و finally در پایتون
ساختار try except را میتوان با دو بلوک اختیاری دیگر گسترش داد: else و finally. استفاده از این دو بلوک، به شما اجازه میدهد کنترل دقیقتری بر جریان برنامه خود داشته باشید و مطمئن شوید که کارهای ضروری همیشه یا فقط در شرایط موفقیتآمیز انجام میشوند.
-
کلمات کلیدی پوشش داده شده: try except else در پایتون، try except finally در پایتون.
۴.۱. بلوک else: اجرای کد تنها در صورت عدم وجود خطا (H3)
بلوک else پس از بلوک try میآید و فقط زمانی اجرا میشود که هیچ استثنایی در بلوک try رخ نداده باشد.
اهمیت آموزشی: این بلوک برای جدا کردن عملیات موفقیتآمیز از کدی که ریسک بالایی دارد، بسیار مفید است. این کار به خوانایی کد کمک میکند و تضمین میدهد که در صورت بروز خطا، آن عملیات موفقیتآمیز هرگز اجرا نمیشود.
مثال کد:
def تقسیم_امن(a, b):
try:
نتیجه = a / b # کد ریسکپذیر
except ZeroDivisionError:
print("خطا: تقسیم بر صفر!")
return None
else:
# این کد فقط زمانی اجرا میشود که در try هیچ خطایی رخ ندهد
print("تقسیم با موفقیت انجام شد.")
return نتیجه
print(تقسیم_امن(10, 2)) # خروجی: تقسیم با موفقیت انجام شد. 5.0
print(تقسیم_امن(10, 0)) # خروجی: خطا: تقسیم بر صفر! None
۴.۲. بلوک finally: تضمین اجرای کد در هر شرایطی (بستن منابع) (H3)
بلوک finally یک بلوک تضمینی است. کدی که در داخل finally قرار میگیرد، بدون توجه به اینکه استثنایی رخ داده باشد یا نه، همیشه اجرا میشود. حتی اگر درون try یا except از دستور return استفاده کنید، finally باز هم قبل از خروج اجرا خواهد شد.
اهمیت فنی (چالش مربیگری پاسخ داده شده): این بلوک برای تمیزکاری (Cleanup) و بستن منابع سیستمی حیاتی است. این منابع شامل بستن اتصال به فایلها، دیتابیسها یا منابع شبکه میشوند. اگر این منابع بسته نشوند، میتوانند منجر به نشت حافظه یا قفل شدن فایل شوند.
مثال کد:
فایل = None
try:
فایل = open("دادهها.txt", "r")
# کد خواندن/پردازش فایل
محتوا = فایل.read()
print("فایل با موفقیت خوانده شد.")
except FileNotFoundError:
print("اخطار: فایل مورد نظر پیدا نشد.")
finally:
# این بلوک همیشه اجرا میشود، چه خطا رخ بدهد و چه ندهد
if فایل: # اطمینان از اینکه فایل اصلا باز شده است
فایل.close()
print("عملیات تمیزکاری: فایل بسته شد.")
جمعبندی ساختار: با ترکیب این چهار بلوک، شما میتوانید ساختار کاملی برای Exception Handling بسازید:
-
try: کد ریسکپذیر.
-
except: رسیدگی به خطا.
-
else: اجرای کد در صورت موفقیت try.
-
finally: تمیزکاری و بستن منابع (اجرای تضمینی).
۵. کنترل جریان خطا: ایجاد و انتشار استثنا
تا اینجا یاد گرفتیم چطور با استفاده از try except، خطاهایی را که پایتون ایجاد میکند، مدیریت کنیم. اما یک برنامهنویس حرفهای باید بتواند در شرایط خاصی که خود برنامه تشخیص میدهد، استثنا ایجاد کند و آن را به لایههای بالاتر برنامه یا به کاربر گزارش دهد. این کار به شما امکان میدهد تا یک جریان خطای کاملاً سفارشی و معنیدار در برنامه خود تعریف کنید.
-
کلمات کلیدی پوشش داده شده: raise Exception در پایتون، ساخت استثنای سفارشی در پایتون.
۵.۱. انتشار مجدد خطاها با raise در پایتون (H3)
دستور raise به شما این قدرت را میدهد که به صورت دستی یک استثنا را در هر نقطه از کد خود ایجاد (پرتاب) کنید. این دستور کاربرد حیاتی در دو سناریوی زیر دارد:
الف) ایجاد یک استثنای جدید برای اعتبار سنجی (Validation)
اگر یک شرط منطقی نقض شود (مثلاً یک تابع یک ورودی منفی دریافت کند، در حالی که فقط باید ورودی مثبت بگیرد)، میتوانید خودتان یک خطا ایجاد کنید تا از پردازش داده نامعتبر جلوگیری کنید.
مثال کد:
def محاسبه_فاکتوریل(عدد):
اگر عدد < 0:
# ایجاد یک استثنای ValueError با پیام دلخواه
raise ValueError("ورودی فاکتوریل نمیتواند منفی باشد.")
# اگر خطا رخ ندهد، کد ادامه پیدا میکند
print("محاسبه انجام شد...")
# نحوه استفاده:
try:
محاسبه_فاکتوریل(-5)
except ValueError as e:
print(f"مدیریت خطا: {e}")
# خروجی: مدیریت خطا: ورودی فاکتوریل نمیتواند منفی باشد.
ب) انتشار مجدد (Re-raising) یک استثنای مدیریت شده
گاهی اوقات شما یک خطا را با except میگیرید، اما بعد از انجام یک عملیات ضروری (مثل ثبت خطا در فایل گزارش)، میخواهید همان خطا دوباره منتشر شود تا لایههای بالاتر برنامه هم از آن مطلع شوند. برای این کار، فقط کافی است دستور raise را بدون هیچ آرگومانی در بلوک except استفاده کنید.
۵.۲. تعریف و استفاده از استثناهای سفارشی (Custom Exceptions) (H3)
برای ساختن برنامههای بزرگ و ماژولار، بهتر است به جای استفاده از Exception یا ValueError، استثناهای خودتان را تعریف کنید. این کار باعث میشود کدی که از تابع شما استفاده میکند، دقیقاً بداند چه خطایی مربوط به منطق برنامه شماست.
نحوه ساختن استثنای سفارشی:
در پایتون، برای ساخت یک استثنای جدید، کافی است یک کلاس جدید تعریف کنید که از کلاس پایه Exception (یا یکی از زیرکلاسهای آن) ارث ببرد.
-
کلمات کلیدی پوشش داده شده: Custom Exceptions در پایتون.
مثال کد:
# تعریف استثنای سفارشی
class موجودی_کافی_نیست(Exception):
"""استثنایی برای زمانی که موجودی حساب کافی نیست."""
pass
# استفاده از استثنای سفارشی
def برداشت_از_حساب(مقدار_برداشت, موجودی_کنونی):
if مقدار_برداشت > موجودی_کنونی:
# ایجاد استثنای سفارشی
raise موجودی_کافی_نیست("موجودی شما برای این عملیات کافی نیست.")
# ... اجرای عملیات برداشت
# نحوه مدیریت:
try:
برداشت_از_حساب(200, 150)
except موجودی_کافی_نیست as e:
print(f"تراکنش ناموفق: {e}")
نتیجه: با تعریف استثناهای سفارشی، شما کنترل کامل بر جریان خطا خواهید داشت و میتوانید پیامهای فنی و آموزشی بسیار دقیقتری را به برنامهنویسان ارائه دهید. این بالاترین سطح از مدیریت خطا در پایتون است.
مثالهای عملی
1: مدیریت ورودیهای کاربر
نوشتن یک برنامه که از کاربر عددی را دریافت کند و در صورت وارد کردن ورودی اشتباه، از کاربر بخواهد که دوباره تلاش کند.
def get_number():
while True:
try:
number = int(input("لطفا یک عدد وارد کنید: "))
return number
except ValueError:
print("ورودی نامعتبر بود. لطفا یک عدد صحیح وارد کنید.")
num = get_number()
print(f"عدد وارد شده: {num}")
2: محاسبه تقسیم با مدیریت استثنا
نوشتن برنامهای که دو عدد را از کاربر دریافت کرده و حاصل تقسیم آنها را نمایش دهد، در صورتی که تقسیم بر صفر اتفاق بیفتد، پیامی به کاربر نشان دهد.
def divide():
try:
x = int(input("عدد اول را وارد کنید: "))
y = int(input("عدد دوم را وارد کنید: "))
result = x / y
print(f"نتیجه تقسیم: {result}")
except ZeroDivisionError:
print("نمیتوان بر صفر تقسیم کرد!")
except ValueError:
print("لطفا از اعداد صحیح استفاده کنید.")
divide()
3: خواندن فایل با مدیریت خطا
نوشتن برنامهای که فایلی را باز کند و محتوای آن را نمایش دهد، در صورتی که فایل وجود نداشته باشد، پیامی به کاربر نشان دهد.
try:
with open("file.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("فایل پیدا نشد!")
4: پرتاب استثنا در صورت ورود سن نامعتبر
یک تابع بنویسید که سن کاربر را دریافت کند و اگر سن کمتر از 18 بود، استثنای سفارشی پرتاب کند.
class AgeError(Exception):
pass
def check_age(age):
if age < 18:
raise AgeError("سن باید حداقل 18 باشد!")
try:
check_age(15)
except AgeError as e:
print(e)
5: ثبت نام کاربر با مدیریت استثنا
برنامهای بنویسید که از کاربر نام و رمز عبور دریافت کرده و اگر رمز عبور کمتر از 6 کاراکتر بود، خطا دهد.
def register_user():
username = input("نام کاربری را وارد کنید: ")
password = input("رمز عبور را وارد کنید: ")
try:
if len(password) < 6:
raise ValueError("رمز عبور باید حداقل 6 کاراکتر باشد!")
print("ثبت نام موفقیتآمیز!")
except ValueError as e:
print(e)
register_user()
تمرین
1. تمرین ورودی اشتباه کاربر
- یک برنامه بنویسید که از کاربر بخواهد یک عدد صحیح وارد کند. اگر ورودی کاربر عدد نباشد، برنامه باید از کاربر بخواهد دوباره تلاش کند تا عدد صحیح وارد کند.
2. تمرین تقسیم دو عدد
- یک برنامه بنویسید که دو عدد از کاربر بگیرد و حاصل تقسیم آنها را محاسبه کند. اگر کاربر تلاش کند عدد را بر صفر تقسیم کند، باید پیامی مناسب به او نشان دهد.
3. تمرین خواندن فایل
- برنامهای بنویسید که نام یک فایل را از کاربر دریافت کرده و سعی کند آن را باز کند. اگر فایل وجود نداشت، برنامه باید پیامی به کاربر نمایش دهد که فایل پیدا نشد.
4. تمرین ثبتنام با رمز عبور ضعیف
- یک برنامه بنویسید که از کاربر نام کاربری و رمز عبور دریافت کند. اگر رمز عبور کوتاهتر از 6 کاراکتر باشد، باید از کاربر بخواهد که رمز عبور جدیدی وارد کند.
5. تمرین استثنای سفارشی
- یک کلاس سفارشی برای خطای "سن نامعتبر" بسازید که در صورت وارد کردن سنی کمتر از 18 سال، این استثنا را پرتاب کند. برنامه باید پیامی مناسب به کاربر نشان دهد.