دیباگ کردن

در برنامه هایی که تحت وب عمل نمی کنند(وب سرور وجود ندارد!) میتوانیم از روش های Debugging برای رفع مشکلات استفاده کنیم.


دیر یا زود و مرتبا به شرایطی برخواهید خورد که برنامه خلاف انتظار و خواسته ی شما عمل می کند. در این مواقع باید مجددا source code را بررسی کنیم و بفهمیم که چرا بین انتظار ما و عملکرد برنامه اختلاف وجود دارد. به این فرآیند در توسعه ی نرم افزار debug یا troubleshoot مشکل گفته می شود.

Log Tracing

اولین قدم یک برنامه نویس حرفه ای برای رفع مشکل بررسی لاگ ها و خروجی های برنامه در بخش های مختلف است. اگر لاگ ها کافی نباشند اولین قدم افزایش سطح نمایش لاگ ها( logging verbosity ) است. این کار را در فصل 6 یاد گرفتیم. اگر با این کار مشکل حل نشد، قدم بعدی بهبود لاگ ها و افزایش تعداد آنها در برنامه است تا بتوانیم با اجرای مجدد برنامه، عملکرد آن را در ترمینال یا خروجی رصد کنیم. به این کار (بررسی عملکرد برنامه با لاگ ها تا زمان پیدا کردن و رفع خط ) Log Tracing گفته می شود.

Debugging

اگر مشکل با Log Tracing حل نشود باید ورودی و خروجی های برنامه را در لحظه های مختلف بررسی کنیم. این کار را در پایتون با Python debugger یعنی pdb انجام می دهیم.

ماژول pdb یک ابزار cli پایتون است که به ما اجازه می دهد در زمان اجرای برنامه به بخش های مختلف آن سرک بکشیم و وضعیت ( state ) برنامه را در لحظات مختلف رصد کنیم، متغیرهای خود را در زمان اجرا ببینیم و روند اجرای برنامه( flow ) را کنترل کنیم.


نکته:

اگر کد Bash مینویسید احتمالا با gdb آشنا باشید. عملکرد pdb مشابه دیگر ابزار مشابه مثل gdb است اما pdb یک ابزار سطح بالاتر و ساخته شده برای پایتون است.


دو راه برای استفاده از pdb وجود دارد. راه مستقیم با دستور pdb به عنوان دستور cli و راه غیر مستقیم با دستور breakpoint در خود برنامه. بیاید هر دو راه را بررسی کنیم. توابع زیر را در نظر بگیرید:

# This is a comment

to_see_in_pdb = "first line of program"

def absolute(number):
    if number <= 0:
        return number * -1
    else:
        return number

def power_operation(x, y):
    res = x ** y
    res = absolute(res)
    return res

print(power_operation(2, 10))

برنامه را ذخیره کرده(به نام power_operation.py ) و در ترمینال به آدرس دایرکتوری آن رفته و دستور زیر را اجرا کنید و خروجی را ببینید(خط اول دستور و 3 خط بعدی خروجی هستند):

$ python –m pdb power_operation.py
[...]power_operation.py(3)<module>()
-> to_see_in_pdb = "first line of program"
(Pdb)

میبینیم که برنامه بعد از رسیدن به دستور اول، قبل از اجرای آن متوقف می شود و وارد محیط pdb برای ارتباط و تصمیم گیری می شویم. خط اول خروجی فایل فعلی ، خط دوم دستوری که هنوز اجرا نشده و خط سوم pdb prompt را به ما نمایش میدهد.

این ترمینال را بسته یا متوقف کنید.


نکته:

در کد های قدیمی پایتون عملکرد pdb را تنها به شکل زیر در خواهید دید. از نسخه ی 3.7 این روش منسوخ شده است:

import pdb
pdb.set_trace()


برای دیدن روش دوم برنامه ی قبل را به صورت زیر تغییر دهید و آن را به نام power_operation_with_breakpoint.py ذخیره کنید:

# This is a comment

to_see_in_pdb = "first line of program"

def absolute(number):
    breakpoint()
    if number <= 0:
        return number * -1
    else:
        return number

def power_operation(x, y):
    res = x ** y
    res = absolute(res)
    return res

print(power_operation(2, 10))

دستور زیر را اجرا کنید تا برنامه در زمان رسیدن به ()breakpoint وارد pdb شود:

$ python power_operation_with_breakpoint.py
> [...]power_operation_with_breakpoint.py(7)absolute()
-> if number <= 0:
(Pdb)

ترمینال را نبندید! در ترمینال pdb دستور help را وارد کنید تا دستورات داخلی pdb و توضیحات آن ها را ببینید:

  • دستور <break <filename>:<linenumber : این دستور یک breakpoint را در یک خط مشخص قرار می دهد. برنامه ی شما ادامه پیدا میکند تا به آن خط برسد و قبل از اجرای آن خط دوباره متوقف می شود و وارد محیط pdb می شوید. به جای filename میتوانید فایل های داخلی پروژه ی خود یا فایل های کتابخانه های استاندارد را قرار دهید.
    مثلا برای توقف در خط 32 ماژول parser از کتابخانه ی استاندارد html :
break html/parser:32
# or 
b html/parser:32
  • دستور <break <function : در اینجا نام یک تابع را قرار میدهیم تا زمانی که در برنامه به تابع رسیدیم breakpoint رخ دهد و تابع اجرا نشود. مثلا تابع html.parser.HTMLParser.feed به عنوان ورودی باعث ایجاد breakpoint در تابع feed در کلاس HTMLParser از ماژول html.parser می شود.
break html.parser.HTMLParser.feed
# or 
b html.parser.HTMLParser.feed
  • دستور break : بدون هیچ ورودی خاصی لیست تمام breakpoint های فعلی برنامه را که تا به حال توسط دستور break تنظیم شده اند یا در کد قرار دارند باز می گرداند.
break
# or 
b
  • دستور continue (یا cont یا c ): در صورت مواجهه با این دستور pdb به اجرای مجدد ادامه ی برنامه می پردازد تا با breakpoint بعدی مواجه شود. این ویژگی بسیار کاربردی و مهم است و در کار با دیباگرهای ترمینالی و دارای رابط کاربری، میتوانید به راحتی با استفاده از این ویژگی برنامه را دیباگ کنید.
  • دستور where : یک stack trace از خط فعلی که debugger (در اینجا pdb همان debugger است. )آنرا متوقف کرده است نمایش می دهد.
  • دستورات down و up : این دو دستور امکان حرکت در stack trace را به ما میدهند. دستور up اجرا کننده ی یک تابع متوقف شده را باز میگرداند و دستور down وارد تابع برای بررسی stack trace می شود.
  • دستور list (یا l (حرف اِل کوچک)): 11 خط از کد را ابتدای breakpoint تا کدهایی که به ترتیب باعث اجرای دستورات منجر به breakpoint شده اند نمایش می دهد. با تکرار مجدد این دستور پس از دیدن خروجی آن 11 خط بعدی نمایش داده می شوند.
  • دستور longlist (یا ll ): source code تابع فعلی را باز میگرداند.
  • دستور next : خط فعلی را اجرا و وارد خط بعد شده ولی آن را اجرا نمیکند.
  • دستور step : خط فعلی را اجرا کرده و تا همان سطح به اجرای آن ادامه میدهد؛ یعنی اگر وارد یک تابع شویم، کل تابع را اجرا کرده و با رسیدن به تابع دیگری در سطح تابع اول متوقف می شود.
  • دستور p : المان مشخص شده ی فعلی را در خروجی نمایش می دهد.
  • دستور pp : المان مشخص شده را به شکل pretty print نمایش می دهد.
  • دستور run و restart : این دو دستور مجددا برنامه را اجرا کرده ولی breakpoint هایی که از قبل مشخص شده اند را در برنامه نگه می دارند. در واقع تا از محیط pdb خارج نشویم و صرفا از دستورات خود pdb استفاده کنیم، تمام breakpoint هایی که تعریف کرده ایم در برنامه باقی می مانند.