فصل ۶: فایل‌ها و خطاها — pathlib، UTF-8، CSV، JSON و logging

try/except/else/finally و سلسله‌مراتب استثناها

خطا بخشی از برنامه است، نه پایانش

کاربر به‌جای عدد «دوازده» تایپ می‌کند، فایل وجود ندارد، اینترنت قطع است. در پایتون این رویدادها استثنا (exception) ایجاد می‌کنند؛ اگر کسی آن را نگیرد، برنامه با یک traceback متوقف می‌شود. traceback را از پایین بخوانید: خط آخر نوع و پیام خطاست و خطوط بالاتر مسیر فراخوانی تا محل خطا.

ساختار کامل

import json
from pathlib import Path

def load_orders(path: Path) -> list:
    try:
        text = path.read_text(encoding="utf-8")
        orders = json.loads(text)
    except FileNotFoundError:
        print("فایل سفارش‌ها هنوز ساخته نشده؛ از لیست خالی شروع می‌کنیم.")
        return []
    except json.JSONDecodeError as e:
        print(f"فایل خراب است (خط {e.lineno}): {e.msg}")
        raise
    else:
        print(f"{len(orders)} سفارش بارگذاری شد.")
        return orders
    finally:
        print("پایان تلاش برای خواندن.")
بخشکی اجرا می‌شود
tryکدی که ممکن است خطا بدهد؛ تا حد ممکن کوتاه
except Xفقط اگر خطای نوع X (یا زیرکلاس‌هایش) رخ دهد
elseفقط اگر try بدون خطا تمام شود
finallyهمیشه؛ با خطا، بدون خطا، حتی بعد از return

چرا else؟ چون کدی که فقط در صورت موفقیت اجرا می‌شود نباید داخل try باشد؛ وگرنه ممکن است خطای آن کد را هم به‌اشتباه بگیرید و پنهان کنید.

سلسله‌مراتب استثناها

BaseException
 ├── KeyboardInterrupt        # Ctrl+C
 ├── SystemExit               # sys.exit()
 └── Exception                # همه‌ی خطاهای «عادی»
      ├── ValueError          # نوع درست، مقدار غلط: int("دوازده")
      ├── TypeError           # نوع غلط: "۵" + 5
      ├── LookupError
      │    ├── KeyError       # کلید دیکشنری نیست
      │    └── IndexError     # اندیس لیست بیرون از بازه
      ├── OSError
      │    ├── FileNotFoundError
      │    └── PermissionError   # فایل در اکسل باز و قفل است
      ├── ZeroDivisionError
      └── ...

گرفتن یک کلاس، همه‌ی زیرکلاس‌هایش را هم می‌گیرد: except LookupError هم KeyError و هم IndexError را. ترتیب except ها مهم است؛ خاص‌تر را بالاتر بگذارید.

بدترین الگو

try:
    process()
except:            # هرگز!
    pass

except: خالی حتی Ctrl+C را می‌گیرد و همراه pass هر باگی را بی‌صدا دفن می‌کند. فقط خطاهایی را بگیرید که می‌دانید چطور مدیریتشان کنید؛ بقیه باید بالا بروند و دیده شوند.

نکته‌هایی که کمتر کسی می‌داند

  • چند نوع را با تاپل در یک except بگیرید: except (ValueError, TypeError) as e:.
  • raise خالی داخل except همان خطا را با traceback اصلی دوباره پرتاب می‌کند؛ برای «ثبت کن و بگذار برود» عالی است.
  • اگر در finally از return استفاده کنید، هر خطای در حال انتشار بی‌صدا بلعیده می‌شود؛ پایتون ۳.۱۴ برای همین هشدار SyntaxWarning می‌دهد.
  • متغیر e در except ... as e بعد از بلوک except پاک می‌شود؛ اگر لازمش دارید، در متغیر دیگری ذخیره کنید.
  • سبک پایتونیک «EAFP» است: به‌جای بررسی if path.exists() و بعد باز کردن (که بینشان ممکن است فایل حذف شود)، مستقیم باز کنید و FileNotFoundError را بگیرید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.