خطای خودتان را بسازید و ردپا بگذارید
گرفتن خطا نیمی از ماجراست؛ نیم دیگر پرتاب خطای معنادار است. تابعی که ورودی نامعتبر میگیرد نباید مقدار عجیبی مثل -1 یا None برگرداند که فراخواننده فراموش کند بررسیاش کند؛ باید با raise صریحاً اعلام کند.
def rug_area(width_cm: int, length_cm: int) -> float:
if width_cm <= 0 or length_cm <= 0:
raise ValueError(f"ابعاد نامعتبر: {width_cm}×{length_cm}")
return width_cm * length_cm / 10_000
استثنای سفارشی
وقتی برنامه بزرگ میشود، خطاهای «کسبوکاری» را از خطاهای فنی جدا کنید. یک کلاس پایه برای پروژه بسازید و خطاهای خاص را از آن مشتق کنید (کلاسها را در فصل بعد عمیق میبینیم؛ اینجا فقط الگو را حفظ کنید):
class OrderError(Exception):
"""پایهی همهی خطاهای مربوط به سفارش."""
class OutOfStockError(OrderError):
def __init__(self, code: str, requested: int, available: int):
super().__init__(f"موجودی {code} کافی نیست: {requested} درخواست، {available} موجود")
self.code = code
self.requested = requested
self.available = available
try:
raise OutOfStockError("KSH-101", 5, 2)
except OrderError as e: # همهی خطاهای سفارش را یکجا بگیر
print(e, "| کد:", e.code)
حالا لایهی رابط کاربری میتواند با یک except OrderError پیام مناسب نشان دهد و باگهای واقعی (TypeError و…) همچنان بالا بروند و دیده شوند.
زنجیرهی علت: raise ... from
def parse_qty(text: str) -> int:
try:
return int(text)
except ValueError as e:
raise OrderError(f"تعداد نامعتبر: {text!r}") from e
با from e خطای اصلی در traceback بهعنوان «علت مستقیم» نمایش داده میشود و اطلاعات دیباگ گم نمیشود.
logging: print حرفهای
print برای پیام به کاربر است، نه برای ردگیری رفتار برنامه. ماژول logging سطح اهمیت، زمان، نام ماژول و مقصد (فایل، کنسول) دارد و بدون حذف کد میتوانید سطح جزئیات را کم و زیاد کنید:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
filename="app.log",
encoding="utf-8", # بدون این، فارسی در ویندوز خراب میشود
)
log = logging.getLogger(__name__)
log.info("سفارش %s ثبت شد", "KSH-101")
log.warning("موجودی %s کمتر از ۳ تخته است", "TBZ-220")
try:
1 / 0
except ZeroDivisionError:
log.exception("خطا در محاسبهی تخفیف") # traceback کامل را هم ثبت میکند
| سطح | کاربرد |
|---|---|
| DEBUG | جزئیات برای توسعهدهنده |
| INFO | رویدادهای عادی: «سفارش ثبت شد» |
| WARNING | غیرعادی ولی قابل ادامه (سطح پیشفرض) |
| ERROR / CRITICAL | شکست یک عمل / شکست کل برنامه |
نکتههایی که کمتر کسی میداند
- در log پیام را با
%sو آرگومان جدا بدهید، نه f-string؛ اگر آن سطح غیرفعال باشد، رشته اصلاً ساخته نمیشود و ابزارهای جمعآوری لاگ پیامهای همشکل را گروهبندی میکنند. basicConfigفقط بار اول اثر دارد؛ اگر کتابخانهای قبل از شما logging را پیکربندی کرده باشد، تنظیمات شما نادیده گرفته میشود. در این حالتforce=Trueبدهید.e.add_note("سفارش مشتری: رضا")(3.11+) به خطای موجود یادداشت اضافه میکند که در traceback چاپ میشود، بدون ساختن استثنای جدید.- نام کلاس استثنای سفارشی را با
Errorتمام کنید (قرارداد PEP 8) و مستقیماً ازExceptionارث ببرید، نه ازBaseException. assertبرای اعتبارسنجی ورودی کاربر نیست: با اجرایpython -Oهمهی assert ها حذف میشوند. برای ورودیها از raise استفاده کنید.