لیست مطالب

خطایابی(Error Handling) در پایتون

مدیریت خطا و باگ در پایتون

حتماً برای شما پیش آمده است که هنگام اجرای یک برنامهٔ پایتونی با یک خطا (Error) مواجه شوید که باعث توقف برنامه می‌شود. این گونه خطاها که حین اجرای برنامه رخ می‌دهند در اصطلاح «استثنا» (Exception) نام دارند. به عبارت دیگر، در زبان پایتونException (استثنا) به شرایطی گفته می‌شود که کد از نظر  syntax درست است اما در زمان اجرا به مشکلی برمی‌خورد و یک خطا رخ می‌دهد. برای مثال، تقسیم یک عدد بر صفر منجر به بروز Exception (استثنا) ZeroDivisionError می‌شود. همچنین، تلاش برای باز کردن فایلی که وجود ندارد یک استثنای FileNotFoundError (خطای «فایل پیدا نشد») را ایجاد خواهد کرد. اگر چنین Exception (استثنا)هایی (ارور ها) رسیدگی نشوند، برنامه با نمایش پیغام خطا متوقف می‌گردد.

خوشبختانه پایتون سازوکاری به نام بلوک try … except فراهم کرده است تا به کمک آن بتوانیم چنین خطاهای زمان اجرا را گرفته و مدیریت کنیم. با استفاده از try بخشی از کد را که احتمال خطا دارد “امتحان” می‌کنیم و توسط except مشخص می‌کنیم که در صورت وقوع یک نوع خطای مشخص، برنامه چگونه واکنش نشان دهد. به این ترتیب به جای توقف برنامه، خودمان تصمیم می‌گیریم که در برابر شرایط غیرمنتظره چه اقدامی انجام شود (مثلاً چاپ یک پیام مناسب، نادیده گرفتن خطا، انجام عملیات جایگزین و غیره).

این مقاله به زبانی ساده و خودمانی شما را با ساختار try-except در پایتون آشنا می‌کند. ابتدا ساختار پایه‌ی try/except و نحوه‌ی عملکرد آن را توضیح می‌دهیم. سپس به نحوه‌ی مدیریت چند نوع خطا به صورت همزمان خواهیم پرداخت. در ادامه کاربرد بخش‌های تکمیلی مثل else و finally را در بلوک‌های Exception (استثنا) بررسی می‌کنیم و خواهیم دید چگونه می‌توان از آن‌ها برای کنترل بهتر جریان برنامه و انجام عملیات پایانی (نظیر آزادسازی منابع) استفاده کرد. در پایان نیز به روش تعریف «خطاهای سفارشی» (تعریف Exception جدید توسط کلاس‌های پایتون) می‌پردازیم. تمام این مباحث همراه با مثال‌های ساده و کاربردی ارائه می‌شوند تا برای مبتدیان قابل درک باشد، و در عین حال نکات حرفه‌ای و ظریفی مطرح خواهد شد که برای برنامه‌نویسان باتجربه نیز مفید است.

ساختار پایه‌ی try و except

ساختار کلی مدیریت خطا در پایتون به شکل یک بلوک try به همراه یک یا چند بلوک except است. به این صورت که ابتدا کلمهٔ کلیدی try آمده و سپس مجموعه‌ای از دستورات که ممکن است باعث خطا شوند در یک بلوک تورفته قرار می‌گیرند. پس از آن یک یا چند except می‌آید تا انواع خطاهای خاص را گرفته و واکنش مناسب نشان دهد. هنگام اجرا، برنامه ابتدا کدهای درون بلاک try را به ترتیب اجرا می‌کند و به محض برخورد به اولین خطایی که رخ دهد، ادامه‌ی بلاک try متوقف شده و کنترل برنامه به اولین بلوک except سازگار با آن خطا منتقل می‌شود. در بلوک except تعیین می‌کنیم که در صورت وقوع خطای مشخص‌شده چه کاری انجام شود (مثلاً چاپ یک پیام خطا). اگر هیچ خطایی در بلاک try رخ ندهد، هیچ‌یک از بلوک‌های except اجرا نمی‌شوند و اجرای برنامه از ادامه‌ی پس از بلوک try ادامه پیدا می‌کند.

برای درک بهتر، به مثال ساده‌ی زیر توجه کنید که تلاش می‌کند یک تقسیم را انجام دهد و خطای «تقسیم بر صفر» را مدیریت می‌کند:

				
					  
y = 0  # فرض کنید این مقدار از ورودی کاربر یا محاسبه دیگری به‌دست آمده است  
try:  
        result = x / y  
        print("نتیجه:", result)  
except ZeroDivisionError:  
        print("خطا: نمی‌توان یک عدد را بر صفر تقسیم کرد.")  


				
			

توجه داشته باشید که در مثال بالا ما نوع خطای مشخص (ZeroDivisionError) را در قسمت except ذکر کرده‌ایم. این کار باعث می‌شود فقط همان خطای خاص گرفته شود. بهترین روش این است که تا حد امکان خطاهای مشخص را گرفته و از استفاده از یک بلوک except کلی بدون ذکر نوع خطا پرهیز کنید، زیرا در غیر این صورت ممکن است جلوی مشاهده و رفع خطاهای غیرمنتظره را بگیرید. در بخش‌های بعدی، روش مدیریت چند نوع خطا و نیز نحوه‌ی گرفتن کلیهٔ خطاها را بیشتر بررسی خواهیم کرد.

مدیریت چند نوع خطا

در بسیاری از موارد، یک قطعه کد ممکن است انواع مختلفی از خطاها را ایجاد کند که هر کدام نیاز به برخورد متفاوت دارند. در زبان پایتون می‌توان پس از یک try چندین بلوک except داشت تا هر کدام یک نوع Exception (استثنا) مشخص را پردازش کنند. به محض وقوع یک Exception (استثنا) در بلاک try، تنها اولین بلوک except که نوع آن خطا را تطبیق می‌دهد اجرا می‌شود و بقیه بلوک‌های except نادیده گرفته می‌شوند. بنابراین ترتیب قرارگیری بلوک‌های except اهمیت دارد: باید از خطاهای خاص‌تر به سمت خطاهای کلی‌تر پیش برویم. برای مثال، ابتدا خطاهایی مثل ZeroDivisionError یا ValueError را رسیدگی کنید و در انتها یک except عمومی‌تر (مثلاً Exception) را برای سایر موارد پیش‌بینی‌نشده قرار دهید. اگر این ترتیب را رعایت نکنید و یک except عمومی را زودتر بنویسید، جلوی اجرای بلوک‌های except بعدی را می‌گیرد، زیرا خطاها پیش از رسیدن به آنها در همان بلوک عمومی شکار می‌شوند.

مثال زیر برنامه‌ای را نشان می‌دهد که دو عدد را از ورودی می‌گیرد و سه حالت خطای ممکن را مدیریت می‌کند: تقسیم بر صفر، ورودی نامعتبر (غیرعددی)، و یک خطای عمومی برای سایر موارد غیرمنتظره:

				
					
try:  
        x = int(input("عدد اول را وارد کنید: "))  
        y = int(input("عدد دوم را وارد کنید: "))  
        result = x / y  
        print("نتیجه تقسیم:", result)  
except ZeroDivisionError:  
        print("خطا: تقسیم بر صفر امکان‌پذیر نیست.")  
except ValueError:  
        print("خطا: مقدار وارد‌شده معتبر نیست؛ لطفاً عدد صحیح وارد کنید.")  
except Exception as e:  
        print("یک خطای غیرمنتظره رخ داد:", e) 

				
			

در این قطعه کد، ابتدا تلاش می‌کنیم اعداد ورودی را به نوع صحیح (int) تبدیل کرده و سپس تقسیم را انجام دهیم. سه بلوک except پشت سر هم آمده است. اگر کاربر عدد دوم را صفر وارد کند، Exception (استثنا) ZeroDivisionError رخ می‌دهد و فقط بخش except ZeroDivisionError اجرا شده و پیام مربوطه را نمایش می‌دهد. اگر کاربر به جای عدد، یک مقدار غیرعددی وارد کند، تبدیل int با خطای ValueError مواجه می‌شود و در نتیجه بلوک except ValueError اجرا شده و پیام “مقدار معتبر نیست” چاپ می‌شود. بلوک سوم با except Exception as e نقش محافظ را دارد که هر نوع خطای پیش‌بینی‌نشده‌ی دیگری را می‌گیرد؛ مثلاً اگر در حین خواندن ورودی‌ها خطای دیگری رخ دهد که جزو دو مورد بالا نباشد، این بخش اجرا خواهد شد و پیغام خطا به همراه جزئیات آن (متغیر e) نمایش داده می‌شود. با این کار برنامه حتی در مواجهه با خطاهای غیرمنتظره نیز متوقف نشده و حداقل یک پیام کلی چاپ می‌کند.

همچنین می‌توان چند Exception (استثنا) مختلف را در یک بلوک except به صورت همزمان رسیدگی کرد. برای این منظور، نام چندین Exception (استثنا) را داخل یک پرانتز قرار داده و آنها را به عنوان نوع except ذکر می‌کنیم. به عنوان نمونه، اگر بخواهیم خطاهای مربوط به نوع‌دادهٔ نادرست (TypeError) و مقدار نامعتبر (ValueError) را یکسان مدیریت کنیم، می‌توانیم چنین بنویسیم:

				
					except (TypeError, ValueError) as err:  
    print("خطا در ورودی:", err)  

				
			

در این مثال، هر خطایی که از جنس TypeError یا ValueError باشد توسط این یک بلوک گرفته شده و در متغیر err قرار می‌گیرد.

در طراحی بلوک‌های except همواره سعی کنید مواردی را مشخص کنید که انتظار وقوعشان را دارید و تنها در صورت نیاز از یک except کلی برای گرفتن سایر خطاهای غیرمنتظره استفاده کنید. برخورد هدفمند با انواع خطا باعث می‌شود خطاهای برنامه راحت‌تر پیدا و برطرف می شوند و از مخفی ماندن باگ‌های پنهان جلوگیری شود.

‍raise در پایتون

دستور raise در پایتون برای ایجاد یک استثنا که خودمان آن را درست می کنیم به کار می‌رود. به عبارت ساده، هر جای برنامه که متوجه شوید یک وضعیت خطا ی  غیرعادی رخ داده است، می‌توانید با استفاده از raise یک خطا (از نوعی مشخص) ایجاد کنید تا اجرای عادی برنامه متوقف شود و آن خطا اعلام گردد. پس از اجرای raise، پایتون یک Exception (استثنا) درست می‌کند؛ اگر این Exception (استثنا) توسط برنامه‌نویس در جایی از کد (با استفاده از try/except) گرفته نشود، برنامه با پیام خطا متوقف خواهد شد.

مثال: فرض کنید تابعی داریم که فقط با اعداد مثبت سروکار دارد. می‌توانیم در ابتدای تابع بررسی کنیم که ورودی منفی نباشد و در غیر این صورت یک Exception (استثنا) درست کنیم:

				
					x = -5
if x < 0:
        raise ValueError("عدد نمی‌تواند منفی باشد.")

				
			

در این مثال، چون شرط x < 0 برقرار است مقدار x نامعتبر تلقی شده و با دستور raise یک Exception (استثنا) از نوع ValueError پرتاب می‌کنیم. استفاده از Exception (استثنا) ValueError انتخاب مناسبی است زیرا این خطا به‌طور خاص برای مقادیر نامعتبر به کار می‌رود(در پایتون عدد کوچکتر از صفر ارور محسوب نمی شود ولی در مثال ما خطا هست.به همین دلیل  با استفاده از  raise ,این خطا را می سازیم ). نتیجه‌ی اجرای این کد (در صورت عدم رسیدگی به خطا) توقف برنامه همراه با پیام خطای مربوط خواهد بود (در این‌جا: ValueError: عدد نمی‌تواند منفی باشد.). به طور خلاصه، raise ابزاری است که به شما امکان می‌دهد وقوع یک خطا را مشخص کنید و جلوی ادامهٔ روند معمول برنامه را بگیرید. این کار برای جلوگیری از تولید نتایج نادرست یا انجام عملیات ناخواسته در صورت بروز شرایط غیرعادی بسیار ضروری است. همچنین می‌توانیم پیغام مناسبی همراهException (استثنا) ارسال کنیم تا علت خطا مشخص‌تر شود.

کاربرد های raise در پایتون


اعتبارسنجی ورودی (Input Validation)

یکی از کاربردهای متداول raise بررسی صحیح بودن ورودی‌هاست. برای مثال فرض کنید تابعی داریم که باید یک رشتهٔ (str) حاوی یک آدرس ایمیل معتبر دریافت کند. اگر ورودی اصلاً رشته نباشد، با TypeError خطا می‌دهیم؛ اگر الگوی ایمیل را رعایت نکند، با ValueError خطابه ما نشان دهد.

				
					import re

EMAIL_REGEX = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")

def set_email(email):
    """اعتبارسنجی ایمیل کاربر"""
    # ➊ بررسی نوع داده
        if not isinstance(email, str):
            raise TypeError("ایمیل باید از نوع str باشد")

    # ➋ بررسی الگوی ایمیل با عبارت منظم (Regex)
        if not EMAIL_REGEX.match(email):
            raise ValueError("آدرس ایمیل واردشده معتبر نیست")

    # ➌ اگر هر دو شرط بالا عبور کنند
        print(f"✅ ایمیل ثبت شد: {email}")

# نمونه استفاده
try:
        set_email("example@example.com")   # معتبر
        set_email("invalid-email")         # نامعتبر → ValueError
        set_email(12345)                       # نوع نامعتبر → TypeError
except (TypeError, ValueError) as err:
        print(f"❌ خطا: {err}")

				
			

در این مثال، اگر کاربر مقدار غیرعددی یا منفی بدهد، اجرای عادی تابع متوقف شده و استثناء مناسب با پیام دلخواه پرتاب می‌شود. این رفتار به ما کمک می‌کند ورودی‌های ناصحیح را به‌خوبی مدیریت کنیم و در صورت لزوم کاربر یا کد فراخوان را از خطا مطلع کنیم. استفاده از ValueError برای مقادیر نا‌مناسب و TypeError برای نوع داده نامرتبط، مطابق داکیومنت پایتون است.

پردازش فایل (File Processing)

در هنگام کار با فایل‌ها نیز معمولاً پیش از انجام عملیات خواندن یا نوشتن، مسیر فایل را چک می‌کنیم. اگر فایل وجود نداشته باشد، با raise FileNotFoundError یک خطای مناسب ایجاد می‌کنیم. همچنین اگر مسیر موجود باشد اما یک دایرکتوری باشد یا مجوز خواندن نداشته باشیم، می‌توانیم انواع دیگریExcept ها  مثل ValueError یا PermissionError به کار بگیریم. مثلاً در تابع زیر سه حالت مختلف را بررسی کرده‌ایم و در هر حالت Except مناسب رااستفاده  می‌کنیم:

				
					

def validate_file_path(file_path):
    # بررسی وجود مسیر
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"مسیر {file_path} وجود ندارد")
    # بررسی اینکه مسیر فایل است
        if not os.path.isfile(file_path):
            raise ValueError(f"{file_path} یک فایل معتبر نیست")
    # بررسی مجوز خواندن
        if not os.access(file_path, os.R_OK):
           raise PermissionError(f"مجوز خواندن برای فایل {file_path} وجود ندارد")
        return True

# مثال استفاده
try:
        validate_file_path("/tmp/data.txt")
        print("فایل برای خواندن آماده است")
except Exception as e:
        print(f"خطای اعتبارسنجی فایل: {e}")

				
			

در این مثال، اگر مسیر ورودی وجود نداشته باشد، یک FileNotFoundError با پیام دلخواه پرتاب می‌کنیم؛ اگر مسیر بود اما فایل نبود (مثلاً دایرکتوری بود)، از ValueError استفاده می‌کنیم؛ و اگر مجوز خواندن نداشتیم، PermissionError می‌دهیم. این الگو به ما کمک می‌کند قبل از انجام عملیات روی فایل، خطاهای متداول را به‌صورت کنترل‌شده شناسایی کنیم.

تعریف خطاهای سفارشی (Custom Exceptions) در پایتون

پایتون مجموعهٔ غنی‌ای از Exception (استثنا) داخلی (مانند ValueError, TypeError, ZeroDivisionError و غیره) دارد، اما گاهی ممکن است بخواهید خطاهای مخصوص برای برنامهٔ خود را تعریف کنید. برای این کار می‌توانید با تعریف یک کلاس جدید که از کلاس پایهٔ Exception ارث‌بری می‌کند، “Exception (استثنا) سفارشی” خود را بسازید. معمولاً نام کلاس‌های استثنا را طوری انتخاب می‌کنند که بیانگر نوع خطا بوده و اغلب با کلمهٔ “Error” خاتمه می‌یابد.

تعریف یک Exception (استثنا) سفارشی بسیار ساده است. کافیست کلاسی ایجاد کنید که زیرکلاس Exception باشد. درون کلاس می‌توانید در صورت نیاز ویژگی‌ها یا متدهای دلخواه تعریف کنید یا حتی فقط از دستور pass استفاده کنید. در مثال زیر ما یک خطای سفارشی به نام NegativeValueError تعریف کرده‌ایم که قرار است هنگام دریافت ورودی نامعتبر (عدد منفی در جایی که مقدار مثبت نیاز است) استفاده شود:

				
					
class NegativeValueError(Exception):  
    """خطا در صورت استفاده از مقدار منفی غیرمجاز."""  
        pass  

def set_age(age):  
        if age < 0:  
        # در صورت ورودی نامعتبر (سن منفی)، یک استثنا از نوع سفارشی خودمان پرتاب می‌کنیم  
            raise   NegativeValueError("سن نمی‌تواند منفی باشد.")  
        print("سن با موفقیت تنظیم شد:", age)  

try:  
        set_age(-5)  
except NegativeValueError as err:  
        print("خطای سفارشی دریافت شد:", err)  

				
			

در این قطعه کد، ابتدا کلاس NegativeValueError را که زیرمجموعه‌ای از Exception است تعریف کرده‌ایم. سپس تابع set_age اگر ورودی age منفی دریافت کند، با دستور raise یک NegativeValueError (همراه با یک پیام توضیحی) پرتاب می‌کند. در بخش try، این تابع را با یک مقدار نامعتبر (-5) فراخوانی کرده‌ایم که باعث ایجاد Exception (استثنا) سفارشی ما می‌شود. چون این Exception (استثنا) را در بلوک except مشخصاً از نوع NegativeValueError گرفته‌ایم، برنامه به جای توقف، به این بلوک رفته و پیام “خطای سفارشی دریافت شد:” را به همراه جزئیات خطا (متغیر err) چاپ می‌کند.

مزیت استفاده از Exception (استثنا) سفارشی در پایتون این است که می‌توانید شرایط خطایی خاص برنامهٔ خود را به شکل متمایز و خوانا مدل‌سازی کنید. برای مثال، در یک برنامهٔ بانکی می‌توانید InsufficientFundsError (خطای کمبود موجودی) یا در یک برنامهٔ احراز هویت InvalidCredentialsError تعریف کنید و هر کدام را به طور مجزا در بلاک‌های except مربوطه پردازش نمایید. این کار باعث می‌شود کد شما خواناتر شده و خطاهای احتمالی به صورت واضح‌تری تفکیک و مدیریت شوند.

بلوک else در ساختار try-except در پایتون

پایتون امکان استفاده از بخش اختیاری else را نیز در ساختار try-except فراهم کرده است. این بلوک در انتهای مجموعه‌ی exceptها قرار می‌گیرد و تنها در صورتی اجرا می‌شود که هیچ Exception (استثنا) در بلاک try رخ نداده باشد. به عبارت دیگر، اگر همهٔ دستورات داخل try با موفقیت انجام شوند، بخش else اجرا می‌گردد؛ اما اگر هرگونه خطایی در طول try رخ دهد، اجرای try متوقف شده و سراغ except می‌رود و در نتیجه بلوک else نادیده گرفته می‌شود.

ممکن است بپرسید چه نیازی به else داریم؛ چرا نمی‌توان همان کدهای بخش else را بیرون از بلاک try/except نوشت؟ تفاوت ظریف در اینجاست که اگر کدی را بعد از بلوک try/except (خارج از آن) قرار دهید، آن کد در هر صورت پس از اتمام try/except اجرا می‌شود – چه خطایی رخ داده باشد و چه رخ نداده باشد (البته در صورتی که خطا گرفته شده باشد و برنامه متوقف نشده باشد). اما با استفاده از else اطمینان حاصل می‌کنید که این بخش از کد فقط در صورت عدم وقوع هیچ خطا اجرا شود. این کار باعث می‌شود منطق برنامه واضح‌تر گردد و از انجام عملیات در شرایط خطا اجتناب شود. طبق داکیومنت پایتون، استفاده از else بهتر از افزودن کدهای اضافی به انتهای بلاک try است، زیرا این روش جلوی آن را می‌گیرد که شاید آن کدهای اضافه، خطاهایی را بگیرند که در اصل توسط کدهای داخل try ایجاد نشده‌اند.

برای مثال، در قطعه کد زیر ابتدا سعی می‌کنیم فایلی را باز کرده و بخوانیم. اگر عملیات باز کردن فایل موفقیت‌آمیز باشد، در بخش else ادامهٔ پردازش فایل انجام می‌شود. در صورت عدم موفقیت در باز کردن (مثلاً اگر فایل وجود نداشته باشد)، مستقیماً به بخش except رفته و else اجرا نخواهد شد:

				
					try:  
        f = open("data.txt", "r")  
except FileNotFoundError:  
        print("خطا: فایل data.txt یافت نشد.")  
else:  
        text = f.read()  
        print("محتوای فایل:", text)  
        f.close()  

				
			

در این مثال، اگر فایل “data.txt” موجود نباشد، Exception (استثنا) FileNotFoundError رخ داده و پیام خطا در بلوک except نمایش داده می‌شود. ولی اگر فایل با موفقیت باز شود (هیچ خطایی رخ ندهد)، آنگاه بلوک else اجرا شده و محتوای فایل خوانده و چاپ می‌شود و در انتها فایل صریحاً بسته می‌شود. به این ترتیب، کدهای مربوط به پردازش فایل (خواندن و نمایش محتوا) تنها زمانی اجرا می‌شوند که مطمئن باشیم عملیات حساس باز کردن فایل با موفقیت انجام شده است.

استفاده از else به خوانایی کد کمک می‌کند و تفکیک بین منطق “موفقیت‌آمیز” و “خطا” را روشن‌تر می‌سازد. در صورتی که نیازی به انجام کاری در صورت موفقیت کامل بلاک try نداشته باشید، می‌توانید از نوشتن else صرف‌نظر کنید؛ این بخش کاملاً اختیاری است.

بلوک finally و انجام کارهای پایانی

همان‌طور که در عکس مشاهده می‌کنید، بلوک finally بدون توجه به اینکه در طی اجرای بلاک try خطایی رخ بدهد یا نه، در انتها همیشه اجرا می‌شود. به همین دلیل، از این بخش معمولاً برای کارهای ضروری پایانی که باید تحت هر شرایطی انجام شوند (مثل آزاد کردن منابع، بستن فایل‌های باز، تمام شدن اتصال‌های شبکه و موارد مشابه) استفاده می‌گردد.

برای نمونه، در قطعه کد زیر از finally برای اطمینان از بسته‌شدن یک فایل استفاده شده است:

				
					
f = None  
try:  
        f = open("data.txt", "r")  
    # ... (عملیات روی فایل)  
        data = f.read()  
        print("طول داده:", len(data))  
except Exception as e:  
        print("خطایی رخ داد:", e)  
finally:  
        if f:  
            f.close()  
            print("فایل بسته شد.") 

				
			

در اینجا ابتدا تلاش می‌کنیم فایل “data.txt” را باز کرده و محتوای آن را بخوانیم. اگر در حین باز کردن یا خواندن فایل خطایی رخ دهد (مثلاً فایل وجود نداشته باشد یا مشکلی در دسترسی به وجود بیاید)، پیام خطا در بخش except نمایش داده می‌شود. سپس چه خطا رخ داده باشد و چه نه، بخش finally اجرا می‌شود. شرط if f: در finally بررسی می‌کند که متغیر فایل مقداردهی شده باشد (یعنی فایل با موفقیت باز شده باشد) و در این صورت فایل را می‌بندد و پیام «فایل بسته شد.» را نمایش می‌دهد. به این ترتیب حتی در صورت بروز خطا هم منابعی که اشغال شده‌اند (در اینجا فایل باز شده) به درستی آزاد می‌شوند.

توجه کنید که در عمل، استفاده از عبارت مدیریت زمینه with (کانتکست منیجر) برای باز کردن فایل‌ها اغلب راه بهتری است که به صورت خودکار فایل را پس از اتمام کار می‌بندد. اما در اینجا برای نمایش کاربرد finally، از ساختار try/finally به شکل دستی استفاده کردیم. به طور کلی هر زمان بخواهید بخشی از کد حتماً اجرا شود (حتی در صورت وقوع Exception (استثنا))، می‌توانید آن را در بلاک finally قرار دهید.

assert در پایتون

عبارت assert در پایتون راهی بسیار ساده برای «بررسی درست بودن» بخش‌های داخلی برنامه است: شرطی می‌نویسیم و اگر آن شرط برقرار نباشد، پایتون به-طور خودکار خطای AssertionError می دهد. این ابزار برای اشکال‌زدایی(debug) و تست کد هنگام توسعه عالی است،

				
					assert شرط, "پیام خطا اختیاری"

				
			

اگر ‎شرط درست باشد، برنامه بی‌صدا ادامه می‌دهد؛ اگر نادرست باشد خطای AssertionError به همراه پیام اختیاری بالا می‌آید.

چرا از assert استفاده می‌کنیم؟

  • برای اطمینان از فرضیات داخلی (مثل طول لیست پس از یک عملیات) و پیدا کردن باگ‌ها زودتر.یعنی از ‎assert کمک می‌گیریم تا مطمئن شویم چیزی که خودِ برنامه‌نویس تصور می‌کند درست است، واقعاً درست باشد؛ مثلاً بعد از حذف یک عضو، هنوز تعداد خانه‌های لیست همان عددی باشد که انتظار داشتیم. اگر این فرض نقض شود، ‎assert بی‌درنگ خطای AssertionError می‌دهد و باگ را زود نشانمان می‌دهد

  • برای مستندسازی کد؛ خواننده بعدی به‌راحتی می‌بیند چه شرطی باید همواره برقرار باشد.

سه مثال ساده و ملموس

۱. کار روی فهرست و دیکشنری

				
					def get_middle(lst):
        assert len(lst) % 2 == 1, "تعداد عناصر باید فرد باشد"
        mid = len(lst) // 2
        return lst[mid]

names = ["Ali", "Sara", "Mehdi"]
print(get_middle(names))        # Sara

				
			

اگر فهرست طول زوج داشته باشد، در همان لحظه با خطای AssertionError روبه‌رو می‌شویم و باگ را زود پیدا می‌کنیم.

۲. تقسیم پول

				
					def share_money(total, people):
        assert people > 0, "تعداد افراد باید بیش از صفر باشد"
        return total / people

print(share_money(120_000, 3))  # 40000.0

				
			

در تست‌های اولیه مطمئن می‌شویم هیچ تابعی با ‎people = 0‎ فراخوانی نشود.

۳. امتیاز بازی

				
					def add_score(score, delta):
    score += delta
    assert 0 <= score <= 100, "امتیاز باید بین 0 و 100 بماند"
    return score

				
			

اگر امتیاز از محدوده مجاز خارج شود، سریعاً متوقف می‌شویم و دلیل را می‌فهمیم.

اما باید بدانیم در حالت اجرا با ‎python -O‎ به‌کلی غیرفعال می‌شود؛ بنابراین نباید از آن برای اعتبارسنجی ورودیِ کاربر یا داده‌های خارجی استفاده کنیم.

نحوه ی اجرا python -O:

وقتی شما بنویسید:

				
					python -O my_script.py

				
			

یعنی به پایتون می‌گوییم:

برنامه رو به شکل بهینه‌شده اجرا کنید، بعضی چیزای اضافی رو نادیده بگیرید تا کمی سبک‌تر اجرا شوید.”

تغییرات حالت هنگام -O در پایتون:

نادیده گرفتن assert

هر کدی که از دستور assert استفاده کرده باشد، در این حالت اجرا نمی‌شود.
مثلا:

				
					assert x > 0, "x باید مثبت باشد"

				
			

این خط در حالت عادی بررسی می‌شود، ولی با python -O کاملاً نادیده گرفته می‌شود.

چه زمانی مفید است؟

  • وقتی بخواهید assertها را فقط در محیط توسعه فعال داشته باشید.

  • زمانی که بخواهید در محیط production سرعت اجرای برنامه کمی بیشتر شود (اگرچه معمولاً تأثیر آن بسیار کم است).

نکته:از assert برای اعتبارسنجی داده‌ها در برنامه واقعی استفاده نکنید، چون ممکن است در حالت -O کاملاً حذف شوند و برنامه بدون بررسی اجرا شه.

مقایسهٔ سریع: assert در برابر raise

ویژگیassertraise …
هدف اصلیپیدا کردن خطاهای برنامه‌نویس (باگ‌های داخلی)اعلام خطای منطقی یا ورودی نامعتبر به کاربر
فعال در ‎python -O‎؟❌ غیرفعال✅ فعال
نوع خطایی که تولید می‌کندهمیشه AssertionErrorهر استثنای دلخواه
خواناییجمله‌ای کوتاه و شفافنیاز به شرط if و سپس raise

قاعدهٔ کلی: «وقتی شما  مسئول درست بودن شرطی هستید از assert استفاده کنید؛ وقتی خطا ممکن است از بیرون باشد (کاربر، فایل، API)، از raise یا try-except استفاده کنید.

assertو raise و tryوexceptوfinally

دستورکاری که انجام می‌دهدچه زمانی به‌درد می‌خورد؟نکتهٔ مهم
assertشرط را می‌سنجد؛ اگر برقرار نباشد خطای AssertionError می‌سازد و برنامه را متوقف می‌کندوقتی مطمئنیم این شرط باید همیشه درست باشد و می‌خواهیم سریع باگ‌های خودمان را پیدا کنیمدر حالت بهینه‌سازی (python -O) همهٔ assert‌ها نادیده گرفته می‌شوند؛ پس برای بررسی ورودی کاربر مناسب نیستند
raiseعمداً یک استثناء (هر نوعی که بخواهیم) ایجاد می‌کند تا بگوییم شرایط مجاز نیستوقتی ورودی یا وضعیت بیرونی نامعتبر است و باید خطای واضح به لایهٔ بالاتر اعلام شودهمیشه فعال است، چه با ‎-O‎ چه بدون آن
tryبلوکی که ممکن است خطا بدهد را «امتحان» می‌کندهرجا احتمال خطا هست ولی نمی‌خواهیم برنامه کرش کندبدون except یا finally مجاز نیست
exceptاگر در try خطا بیفتد، این بخش اجرا می‌شود و می‌توانیم واکنش مناسب نشان دهیم مدیریت خطاهای پیش‌بینی‌شده (مثلاً خواندن فایل، تقسیم بر صفر)می‌توان چند except برای خطاهای مختلف نوشت
elseفقط وقتی اجرا می‌شود که در try هیچ خطایی رخ نداده باشدجداکردن منطق «موفق» از کد اصلی خطاگیریبعد از تمام except‌ها می‌آید
finallyبدون توجه به وجود خطا، همیشه اجرا می‌شود؛ برای تمیزکاری (بستن فایل، قطع اتصال) به‌کار می‌رودهر زمانی منبعی باز کرده‌ایم و باید مطمئن شویم آزاد می‌شوداگر خطای ذخیره‌شده باشد، بعد از اجرای finally دوباره بالا می‌آید
نوشته های مرتبط

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *