Faragiri

همهٔ زبان‌ها

آموزش خواندن استک‌تریساشکال‌زدایی

گزارش خطا از پایین خوانده می‌شود و بیشتر سطرهایش مال کتابخانه است. دو شماره ساده می‌گویند کدام سطر را باید باز کنید.

۴بخش
۳دقیقه خواندن
۴عمق

یک خطا، هفت سطر

از هفت قاب تا یک سطری که باید باز شود

عرض هر باند عدد خودش است و نه سهمی از باند بالایی؛ هر مرحله چیزی را کنار می‌گذارد که خواندنش وقت می‌برد و کمکی نمی‌کند.

  1. کل گزارشهر دو بخش زنجیره، از قدیمی‌ترین فراخوانی تا آخرین۷ قاب
  2. بخش علت اصلیهمان بخشی که ریشه خرابی در آن است۵ قاب
  3. قاب‌های کد خودتانسه قاب دیگر داخل کتابخانه‌اند و هیچ اشکالی در آن‌ها نیست۲ قاب
  4. سطری که باز می‌کنیدپایین‌ترین سطری که نام فایل خودتان را دارد۱ سطر

این باندها یک گزارش را می‌شمارند. در یک برنامه با چند لایه فریم‌ورک، باند اول ده‌ها برابر این است و باند آخر همچنان یک سطر.

گزارش خطا از پایین خوانده می‌شود. آخرین سطر می‌گوید چه شد و سطرهای بالای آن می‌گویند از کجا آمد، به‌ترتیبی که قدیمی‌ترین فراخوانی اول چاپ می‌شود. سطری که باید باز کنید تقریبا هیچ‌وقت اولین سطر نیست.

یک اسکریپت که سطرهای یک فایل را با json.loads می‌خواند و سطر دوم خراب است، یک خطای زنجیره‌ای می‌سازد. شمردنش با ماژول traceback:

  • len(traceback.extract_tb(e.__traceback__)) جواب می‌دهد ۲
  • len(traceback.extract_tb(e.__cause__.__traceback__)) جواب می‌دهد ۵

آن ۵ قاب همان بخش علت اصلی‌اند. یعنی روی هم ۷ قاب چاپ می‌شود و فقط ۲ قاب مال کد شماست؛ سه قاب باقی‌مانده داخل خود کتابخانه json هستند و در آن‌ها هیچ اشکالی نیست. عادت خواندن این است: از پایین بیایید بالا و اولین سطری که نام فایل خودتان را دارد باز کنید. از هفت قاب، ۱ سطر می‌ماند و تقریبا همیشه همان است.

دو خطا در یک گزارش، و جمله‌ای که مرزشان است

وقتی یک خطا داخل بند except خطای دیگری بالا می‌آورد، پایتون هر دو را چاپ می‌کند و میانشان یک جمله می‌گذارد. اگر raise ... from exc نوشته باشید جمله این است که خطای بالا علت مستقیم خطای پایین بود؛ اگر ننوشته باشید، جمله می‌گوید هنگام رسیدگی به خطای بالا خطای دیگری رخ داد. اولی یعنی ترجمه عمدی و دومی یعنی چیزی در مسیر رسیدگی هم شکسته است.

بخش پایینی گزارش خطای شماست و بخش بالایی علت اصلی. برای پیدا کردن ریشه، بخش بالایی را بخوانید؛ برای فهمیدن اینکه برنامه چه تصمیمی گرفت، بخش پایینی را. نوشتن آن یک کلمه در شکست پرصدا، شکست بی‌خطر، و فرق این دو است.

سه چیزی که در هر سطر گزارش هست

هر سطر File سه چیز دارد: نام فایل، شماره سطر، و نام تابعی که آن سطر داخلش بود. اسم تابع همان جایی است که بیشتر آدم‌ها نگاه نمی‌کنند و بیشترین کمک را می‌کند، چون وقتی یک فایل چند تابع مشابه دارد، شماره سطر به‌تنهایی گمراه‌کننده است.

وقتی گزارش خیلی طولانی است و بیشترش کتابخانه است، ماژول traceback می‌تواند فقط آخرین چند قاب را چاپ کند و در برنامه‌ای که خودش خطا را می‌گیرد این تنظیم ارزش دارد. اما در حالت بازگشتی وارونه است: آنجا وسط گزارش صدها سطر تکراری است و همان تکرار خودش پیام است، که موضوع تابعی که خودش را صدا می‌زند، و جای شرط پایان است.

جایی که کار گیر می‌کند، ترجمه نشدن پیام است

گزارش خطا به انگلیسی است و همین باعث می‌شود بسیاری از تازه‌کارهای فارسی‌زبان اصلا نخوانندش و مستقیم متنش را جای دیگری جست‌وجو کنند. مشکل این نیست که جست‌وجو بد است، این است که جواب مسئله معمولا داخل خود همان متن است و در سطر آخر نوشته شده. سه کلمه‌ای که ارزش یاد گرفتن دارند همین‌اند و بیشتر خطاها یکی از این‌ها هستند: NameError یعنی نامی وجود ندارد، TypeError یعنی نوع نمی‌خواند، و KeyError یعنی کلیدی در نگاشت نیست. با همین سه تا و عادت خواندن از پایین، بیشتر خطاها بدون هیچ جست‌وجویی حل می‌شوند.

ابزار، و سالی که این حرف درست است

در سال ۲۰۲۶ ماژول traceback در کتابخانه استاندارد و رایگان است و جای کپی کردن دستی متن خطا در لاگ را گرفته؛ traceback.format_exception کل زنجیره را با هر دو بخش برمی‌گرداند. برای خواندن راحت‌تر، بسته rich گزارش را رنگی می‌کند و سطرهای کد اطراف هر قاب را هم نشان می‌دهد؛ متن‌باز است و از مخزن نصب می‌شود. از پایتون 3.11 خود مفسر زیر سطر خطا با نویسه‌های نشانگر دقیقا همان بخشی از عبارت را که شکسته علامت می‌زند، که روی یک سطر با چند فراخوانی تودرتو بیشترین کمک را می‌کند.

پرسش بعدی این است که وقتی از روی گزارش فهمیدید کجا شکسته و هنوز نمی‌دانید چرا، چطور همان‌جا بایستید و مقدارها را ببینید، که متوقف کردن برنامه، و نگاه کردن به دور و برش است. و همین فهرست را وقتی برنامه هنوز سالم است هم می‌شود چاپ کرد: برنامه چطور یادش می‌ماند باید کجا برگردد.

کنار این

بالای این