مدیریت خطاها در پایتون همواره بر این اساس بوده است که "در هر لحظه تنها یک خطا رخ میدهد". اما در برنامهنویسی مدرن، بهویژه در برنامههای ناهمگام (Asynchronous) یا هنگام اعتبارسنجیهای پیچیده دادهها، ممکن است چندین خطا همزمان رخ دهند.
پایتون ۳.۱۱ با معرفی Exception Groups و دستور جدید except* این مشکل را حل کرده است. در این مقاله، از مفاهیم پایه تا تکنیکهای پیشرفتهی این قابلیت را بررسی میکنیم.

سطح مقدماتی (Beginner Level)
در سطح مقدماتی، یاد میگیریم که ExceptionGroup چیست، چگونه ایجاد میشود و چطور میتوانیم با استفاده از دستور جدید except* آنها را مدیریت کنیم.
۱. مفهوم ExceptionGroup چیست؟
به زبان ساده، ExceptionGroup جعبهای است که میتواند چندین استثنا (Exception) دیگر را درون خود نگه دارد. قبل از پایتون ۳.۱۱، اگر شما لیستی از خطاها داشتید، نمیتوانستید همه آنها را یکجا raise کنید، مگر اینکه آنها را در یک لیست معمولی بگذارید که استاندارد نبود.
مثال اول: ساخت و پرتاب یک ExceptionGroup
در این مثال میبینیم که چگونه میتوان چندین خطا را بستهبندی کرد. دقت کنید که وقتی این کد اجرا شود، خروجی به شکلی متفاوت از خطاهای معمولی نمایش داده میشود.
مثال دوم: تفاوت ظاهری در Traceback
اگر یک ExceptionGroup مدیریت نشود (uncaught)، پایتون آن را به صورت درختی نمایش میدهد.
# (Static) این کد صرفاً جهت نمایش ساختار است و نیاز به اجرا در محیط تعاملی دارد
raise ExceptionGroup("Errors", [
ValueError("Bad value"),
TypeError("Bad type")
])
# Output sketch:
# + Exception Group Traceback (most recent call last):
# | ...
# | ExceptionGroup: Errors (2 sub-exceptions)
# +-+---------------- 1 ----------------
# | ValueError: Bad value
# +---------------- 2 ----------------
# | TypeError: Bad type
# +------------------------------------
۲. مدیریت خطاها با except*
مهمترین تغییر در سینتکس پایتون برای پشتیبانی از این قابلیت، معرفی except* است. دستور except معمولی (بدون ستاره) کل گروه را به عنوان یک شیء میبیند، اما except* (با ستاره) داخل گروه را میگردد و فقط خطاهایی که با نوع مشخص شده تطابق دارند را استخراج و مدیریت میکند.
مثال اول: استفاده از except*
در اینجا میبینیم که چگونه میتوانیم خطاهای مختلف داخل یک گروه را جداگانه مدیریت کنیم.
مثال دوم: تودرتویی (Nesting)
ExceptionGroupها میتوانند تودرتو باشند. دستور except* به صورت بازگشتی (Recursive) تمام درخت خطا را جستجو میکند تا نوع مورد نظر را پیدا کند.
سطح پیشرفته (Professional Level)
در این بخش به مکانیزم داخلی "تقسیم و انتشار" (Split and Propagate)، استفاده در asyncio و TaskGroup، و نکات ظریف کار با BaseExceptionGroup میپردازیم.
۱. مکانیزم انتشار و فیلترینگ (Bubbling Logic)
وقتی از except* استفاده میکنید، پایتون عملاً شیء ExceptionGroup اصلی را "تکهتکه" میکند.
- خطاهایی که با نوع
except*مطابقت دارند جدا شده و به آن بلاک فرستاده میشوند. - خطاهایی که مطابقت ندارند، در یک
ExceptionGroupجدید باقی مانده و به بیرون پرتاب (Propagate) میشوند.
این رفتار متفاوت از except معمولی است که اگر یک خطا را نگیرد، کل زنجیره را رها میکند. در except* ممکن است بخشی از گروه مدیریت شود و بخشی دیگر باعث کرش برنامه شود.
مثال اول: مدیریت بخشی از خطاها
در کد زیر، ValueError مدیریت میشود اما TypeError مدیریت نمیشود و باعث توقف برنامه (یا پرتاب به لایه بالاتر) میشود.
مثال دوم: ساختار سلسلهمراتبی
توجه داشته باشید که ExceptionGroup از Exception ارثبری میکند، اما BaseExceptionGroup از BaseException. اگر میخواهید خطاهای سیستمی مثل KeyboardInterrupt را در گروه مدیریت کنید، باید به این نکته دقت کنید.
# (Static) تعریف سلسله مراتب
# BaseException
# ├── BaseExceptionGroup
# │ └── ExceptionGroup
# └── Exception
۲. کاربرد در برنامهنویسی ناهمگام (Asyncio TaskGroup)
اصلیترین دلیل اضافه شدن Exception Groups به پایتون، مدیریت خطاهای همزمان در asyncio بود. کلاس asyncio.TaskGroup (جایگزین مدرن gather) از این قابلیت استفاده میکند. اگر چندین Task همزمان خطا دهند، همه آنها در یک ExceptionGroup جمع میشوند.
مثال اول: استفاده از TaskGroup
این کد نشان میدهد چطور دو تسک همزمان خطا میدهند و ما هر دو را میگیریم.
۳. افزودن یادداشت به خطاها (add_note)
پایتون ۳.۱۱ متد add_note() را به تمام استثناها اضافه کرد. این قابلیت در کنار Exception Groups بسیار قدرتمند است، زیرا به شما اجازه میدهد بدون تغییر نوع خطا، اطلاعات زمینهای (Context) را به خطاهای داخل گروه اضافه کنید تا در زمان دیباگ مشخص شود هر خطا مربوط به کدام بخش بوده است.
مثال اول: غنیسازی خطاها در گروه
در این مثال، خطاها را قبل از گروهبندی با اطلاعات اضافی برچسبگذاری میکنیم.
مثال دوم: دستکاری ساختار ExceptionGroup (Split)
شما میتوانید به صورت دستی و با متد split() یک گروه خطا را فیلتر کنید. این همان کاری است که except* در پشت صحنه انجام میدهد.