خطا و استثناها در پایتون


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

مفهوم استثناها (Exceptions) در پایتون

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

مثال:

x = 10
y = 0
z = x / y  # اینجا یک استثنا ایجاد می‌شود چون نمی‌توان بر صفر تقسیم کرد.

خروجی:

ZeroDivisionError: division by zero

ساختار try-except

ساختار `try-except` به ما این امکان را می‌دهد که کدهایی که ممکن است خطا ایجاد کنند را در داخل بلوک `try` قرار دهیم و در صورت بروز خطا، کنترل برنامه به بلوک `except` منتقل می‌شود.

مثال:

try:
    x = 10
    y = 0
    z = x / y
except ZeroDivisionError:
    print("نمی‌توان بر صفر تقسیم کرد!")

خروجی:

نمی‌توان بر صفر تقسیم کرد!

استفاده از else و finally

بلوک else زمانی اجرا می‌شود که در بلوک `try` هیچ استثنایی رخ ندهد، و بلوک `finally` همیشه اجرا می‌شود، حتی اگر استثنایی رخ دهد.

مثال:

try:
    x = 10
    y = 5
    z = x / y
except ZeroDivisionError:
    print("نمی‌توان بر صفر تقسیم کرد!")
else:
    print("تقسیم با موفقیت انجام شد!")
finally:
    print("این بخش همیشه اجرا می‌شود.")

خروجی:

تقسیم با موفقیت انجام شد!
این بخش همیشه اجرا می‌شود.

4. انواع استثناها در پایتون

پایتون انواع مختلفی از استثناها دارد که هر کدام نمایانگر نوع خاصی از خطا هستند. برخی از انواع رایج استثناها عبارتند از:

- ZeroDivisionError: زمانی که تقسیم بر صفر انجام می‌شود.

- FileNotFoundError: زمانی که فایلی که می‌خواهیم باز کنیم وجود ندارد.

- ValueError: زمانی که ورودی نامعتبر به تابعی داده می‌شود.

مثال:

try:
    int("abc")  # تلاش برای تبدیل رشته غیر عددی به عدد
except ValueError:
    print("ورودی نامعتبر بود!")

خروجی:

ورودی نامعتبر بود!

5. ساخت استثناهای سفارشی (Custom Exceptions)

در پایتون می‌توانیم استثناهای خود را با ایجاد کلاس‌هایی که از کلاس `Exception` ارث بری می‌کنند، تعریف کنیم.

مثال:

class MyCustomError(Exception):
    pass

try:
    raise MyCustomError("یک خطای سفارشی ایجاد شد!")
except MyCustomError as e:
    print(e)

خروجی:

یک خطای سفارشی ایجاد شد!

6. مدیریت چندین استثنا با استفاده از چندین except

گاهی اوقات ممکن است چندین نوع استثنا در یک زمان رخ دهند. در این حالت می‌توانیم چندین بلوک `except` برای مدیریت انواع مختلف استثناها تعریف کنیم.

مثال:

try:
    x = int(input("عدد اول را وارد کنید: "))
    y = int(input("عدد دوم را وارد کنید: "))
    z = x / y
except ZeroDivisionError:
    print("نمی‌توان بر صفر تقسیم کرد!")
except ValueError:
    print("ورودی‌ها باید اعداد صحیح باشند!")

خروجی:

ورودی‌ها باید اعداد صحیح باشند!

7. استفاده از raise برای پرتاب استثنا

گاهی اوقات می‌خواهیم در برنامه‌مان یک استثنا ایجاد کرده و آن را پرتاب کنیم. این کار را می‌توان با دستور `raise` انجام داد.

مثال:

def check_age(age):
    if age < 18:
        raise ValueError("سن باید حداقل 18 باشد!")
    else:
        print("سن معتبر است.")

try:
    check_age(15)
except ValueError as e:
    print(e)

خروجی:

سن باید حداقل 18 باشد!

8. خطایابی و رفع اشکال (Debugging)

استفاده از مدیریت استثناها می‌تواند به شناسایی و رفع اشکال در برنامه کمک کند. برای مثال، با استفاده از استثناها می‌توانیم ورودی‌های نادرست یا شرایط غیرمنتظره را شناسایی کنیم و برنامه را به درستی مدیریت کنیم.

مثال:

def divide_numbers(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("نمی‌توان بر صفر تقسیم کرد.")
    except TypeError:
        print("ورودی‌ها باید اعداد باشند.")

# تست با ورودی‌های مختلف
print(divide_numbers(10, 2))  # خروجی: 5.0
print(divide_numbers(10, 0))  # خروجی: نمی‌توان بر صفر تقسیم کرد.
print(divide_numbers(10, "a"))  # خروجی: ورودی‌ها باید اعداد باشند.

9. مدیریت استثنا در توابع و کلاس‌ها

برای مدیریت استثناها در توابع و کلاس‌ها، می‌توانیم از ساختار `try-except` در داخل توابع و متدها استفاده کنیم تا خطاهای احتمالی مدیریت شوند.

مثال:

class Calculator:
    def divide(self, a, b):
        try:
            return a / b
        except ZeroDivisionError:
            print("نمی‌توان بر صفر تقسیم کرد.")

calc = Calculator()
calc.divide(10, 0)

خروجی:

نمی‌توان بر صفر تقسیم کرد.

مثال‌های عملی

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 سال، این استثنا را پرتاب کند. برنامه باید پیامی مناسب به کاربر نشان دهد.