برنامهٔ حرفهای با برنامهٔ تمرینی در همینجا فرق میکند: وقتی چیزی اشتباه پیش میرود، برنامهٔ خوب «تمیز و قابل فهم» میشکند، نه با Traceback بیربط و حالت خراب.
خطا در برابر استثنا
خطای نحوی (SyntaxError) را مفسر قبل از اجرا میگیرد؛ استثنا (Exception) در زمان اجراست و میشود گرفتش. فلسفهٔ پایتون: «درخواست بخشش راحتتر از اجازهگرفتن است» (EAFP) — امتحان کن و استثنا را بگیر، بهجای صد تا شرط احتیاطی.
class InsufficientFundsError(ValueError):
"""موجودی برای برداشت کافی نیست."""
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("مبلغ باید مثبت باشد.")
if amount > balance:
raise InsufficientFundsError(f"موجودی {balance} برای برداشت {amount} کافی نیست.")
return balance - amount
try:
left = withdraw(100, 150)
except InsufficientFundsError as e:
print("خطای دامنهای:", e)
except ValueError as e:
print("ورودی نامعتبر:", e)
else:
print("موجودی باقیمانده:", left)
finally:
print("ثبت در گزارش انجام شد.")
ترتیب except از خاص به عام است؛ وارونه بنویسی، شاخهٔ خاص هیچوقت نمیرسد. else فقط وقتی اجرا میشود که استثنایی رخ نداده باشد — جای تمیزِ «مسیر موفقیت».
انتشار و مرز ترجمه
استثنا تا جایی بالا میرود که گرفته شود. قانون مرز: در لایهٔ پایین استثنای فنی، در مرز ماژول به استثنای دامنهایِ قابلفهم برای کاربرِ آن لایه ترجمهاش کن. هرگز استثنا را قورت نده (except: pass)؛ یا مدیریتش کن یا بگذار بالا برود.
زمینهبان: منبع همیشه آزاد میشود
from contextlib import contextmanager
@contextmanager
def managed_resource(name):
print(f"گرفتن {name}")
try:
yield name
finally:
print(f"آزادکردن {name}")
with managed_resource("قفل") as r:
print("کار با", r)
# حتی اگر وسط کار استثنا بیاید، آزادسازی در finally اجرا میشود
روش اشکالزدایی
۱. بازتولید پایدار (کوچکترین ورودی که خرابی را نشان میدهد). ۲. خواندن Traceback از پایین به بالا (آخرین خط نوع خطاست، بالای آن محل). ۳. فرضیهٔ واحد و آزمایش واحد. ۴. چاپ هدفمند یا breakpoint()، نه چاپ تصادفی در کل کد.
تست واحد
import unittest
from bank import withdraw, InsufficientFundsError
class TestWithdraw(unittest.TestCase):
def test_normal(self):
self.assertEqual(withdraw(100, 40), 60)
def test_overdraft_raises(self):
with self.assertRaises(InsufficientFundsError):
withdraw(100, 150)
def test_negative_rejected(self):
with self.assertRaises(ValueError):
withdraw(100, -10)
unittest استاندارد و بدون وابستگی است؛ pytest نگارش روانتر و اکوسیستم قویتری دارد (assert ساده، فیکسچر، پارامتری). برای دانشجو: unittest را بفهم، و اگر پروژه جدی شد pytest را انتخاب کن — منطق تستنویسی (مرزها، استثناها، نامگذاری روشن) در هر دو یکی است.
سنجش فهم — خطا و تست
۰ از ۳ پاسخ داده شده