Thứ ba, 25/04/2017 | 00:00 GMT+7

Cách sử dụng trình gỡ lỗi Python

Trong phát triển phần mềm, gỡ lỗi là quá trình tìm kiếm và sau đó giải quyết các vấn đề ngăn phần mềm chạy chính xác.

Trình gỡ lỗi Python cung cấp môi trường gỡ lỗi cho các chương trình Python. Nó hỗ trợ cài đặt các điểm ngắt có điều kiện, lướt qua mã nguồn từng dòng một, kiểm tra ngăn xếp và hơn thế nữa.

Làm việc tương tác với Trình gỡ lỗi Python

Trình gỡ lỗi Python xuất hiện như một phần của phân phối Python chuẩn dưới dạng module được gọi là pdb . Trình gỡ lỗi cũng có thể mở rộng và được định nghĩa là lớp Pdb . Bạn có thể đọc tài liệu chính thức của pdb để tìm hiểu thêm.

Ta sẽ bắt đầu bằng cách làm việc với một chương trình ngắn có hai biến toàn cục , một hàm tạo vòng lặp lồng nhau và cấu trúc if __name__ == '__main__': sẽ gọi hàm nested_loop() .

looping.py
num_list = [500, 600, 700] alpha_list = ['x', 'y', 'z']   def nested_loop():     for number in num_list:         print(number)         for letter in alpha_list:             print(letter)  if __name__ == '__main__':     nested_loop()  

Bây giờ ta có thể chạy chương trình này thông qua trình gỡ lỗi Python bằng cách sử dụng lệnh sau:

  • python -m pdb looping.py

Cờ dòng lệnh -m sẽ nhập bất kỳ module Python nào cho bạn và chạy nó dưới dạng tập lệnh. Trong trường hợp này, ta đang nhập và chạy module pdb , module này ta chuyển vào lệnh như được hiển thị ở trên.

Khi chạy lệnh này, bạn sẽ nhận được kết quả sau:

Output
> /Users/sammy/looping.py(1)<module>() -> num_list = [500, 600, 700] (Pdb)

Trong kết quả , dòng đầu tiên chứa tên module hiện tại (như được chỉ ra với <module> ) với đường dẫn folder và số dòng được in theo sau (trong trường hợp này là 1 , nhưng nếu có chú thích hoặc không thể thực thi khác dòng nó có thể là một số cao hơn). Dòng thứ hai hiển thị dòng mã nguồn hiện tại được thực thi ở đây, vì pdb cung cấp một console tương tác để gỡ lỗi. Bạn có thể sử dụng lệnh help để tìm hiểu các lệnh của nó và help command để tìm hiểu thêm về một lệnh cụ thể. Lưu ý console pdb khác với shell tương tác Python.

Trình gỡ lỗi Python sẽ tự động bắt đầu lại khi nó kết thúc chương trình của bạn. Khi nào bạn muốn rời khỏi console pdb , hãy gõ lệnh quit hoặc exit . Nếu bạn muốn khởi động lại chương trình một cách rõ ràng ở bất kỳ vị trí nào trong chương trình, bạn có thể làm như vậy bằng lệnh run .

Sử dụng trình gỡ lỗi để di chuyển qua một chương trình

Khi làm việc với các chương trình trong trình gỡ lỗi Python, bạn có thể sử dụng list , step và lệnh next để chuyển qua mã của bạn . Ta sẽ xem xét các lệnh này trong phần này.

Trong shell, ta có thể nhập list lệnh để lấy ngữ cảnh xung quanh dòng hiện tại. Từ dòng đầu tiên của chương trình looping.py mà ta đã hiển thị ở trên - num_list = [500, 600, 700] - sẽ giống như sau:

(Pdb) list   1  -> num_list = [500, 600, 700]   2     alpha_list = ['x', 'y', 'z']   3        4        5     def nested_loop():   6         for number in num_list:   7             print(number)   8             for letter in alpha_list:   9                 print(letter)  10       11     if __name__ == '__main__': (Pdb)  

Dòng hiện tại được biểu thị bằng các ký tự -> , trong trường hợp của ta là dòng đầu tiên của file chương trình.

Vì đây là một chương trình tương đối ngắn, ta nhận được gần như toàn bộ chương trình trở lại bằng lệnh list . Không cung cấp đối số, lệnh list cung cấp 11 dòng xung quanh dòng hiện tại, nhưng bạn cũng có thể chỉ định dòng nào sẽ bao gồm, như sau:

(Pdb) list 3, 7   3        4        5     def nested_loop():   6         for number in num_list:   7             print(number) (Pdb)  

Ở đây, ta yêu cầu các dòng 3-7 được hiển thị bằng cách sử dụng list 3, 7 lệnh list 3, 7 .

Để chuyển qua từng dòng chương trình, ta có thể sử dụng step hoặc step next :

(Pdb) step > /Users/sammy/looping.py(2)<module>() -> alpha_list = ['x', 'y', 'z'] (Pdb)  
(Pdb) next > /Users/sammy/looping.py(2)<module>() -> alpha_list = ['x', 'y', 'z'] (Pdb)  

Sự khác biệt giữa stepstep nextstep đó sẽ dừng lại trong một hàm được gọi, trong khi bước next thực hiện các hàm được gọi chỉ dừng lại ở dòng tiếp theo của hàm hiện tại. Ta có thể thấy sự khác biệt này khi ta làm việc với hàm.

Lệnh step sẽ lặp lại qua các vòng khi nó chạy hàm, hiển thị chính xác những gì vòng lặp đang làm, vì đầu tiên nó sẽ in một số với print(number) sau đó chuyển sang in các chữ cái với print(letter) , quay lại số, v.v.:

(Pdb) step > /Users/sammy/looping.py(5)<module>() -> def nested_loop(): (Pdb) step > /Users/sammy/looping.py(11)<module>() -> if __name__ == '__main__': (Pdb) step > /Users/sammy/looping.py(12)<module>() -> nested_loop() (Pdb) step --Call-- > /Users/sammy/looping.py(5)nested_loop() -> def nested_loop(): (Pdb) step > /Users/sammy/looping.py(6)nested_loop() -> for number in num_list: (Pdb) step > /Users/sammy/looping.py(7)nested_loop() -> print(number) (Pdb) step 500 > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) step > /Users/sammy/looping.py(9)nested_loop() -> print(letter) (Pdb) step x > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) step > /Users/sammy/looping.py(9)nested_loop() -> print(letter) (Pdb) step y > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) 

Thay vào đó, lệnh next sẽ thực thi toàn bộ chức năng mà không hiển thị quy trình từng bước. Hãy thoát phiên hiện tại bằng lệnh exit và sau đó bắt đầu lại trình gỡ lỗi:

  • python -m pdb looping.py

Bây giờ ta có thể làm việc với lệnh next :

(Pdb) next > /Users/sammy/looping.py(5)<module>() -> def nested_loop(): (Pdb) next > /Users/sammy/looping.py(11)<module>() -> if __name__ == '__main__': (Pdb) next > /Users/sammy/looping.py(12)<module>() -> nested_loop() (Pdb) next 500 x y z 600 x y z 700 x y z --Return-- > /Users/sammy/looping.py(12)<module>()->None -> nested_loop() (Pdb)   

Trong khi xem qua mã của bạn , bạn có thể cần kiểm tra giá trị được truyền cho một biến, điều này bạn có thể thực hiện với lệnh pp , lệnh này sẽ in khá đẹp giá trị của biểu thức bằng mô-đun pprint :

(Pdb) pp num_list [500, 600, 700] (Pdb)  

Hầu hết các lệnh trong pdb có alias ngắn hơn. Đối với step đó dạng ngắn là s , và đối với bước nextn . Lệnh help sẽ liệt kê các alias có sẵn. Bạn cũng có thể gọi lệnh cuối cùng mà bạn đã gọi bằng cách nhấn phím ENTER tại dấu nhắc.

Điểm ngắt

Bạn thường sẽ làm việc với các chương trình lớn hơn ví dụ trên, vì vậy bạn có thể sẽ muốn xem xét các hàm hoặc dòng cụ thể hơn là xem qua toàn bộ chương trình. Bằng cách sử dụng lệnh break để cài đặt các điểm ngắt, bạn sẽ chạy chương trình cho đến khi điểm ngắt được chỉ định.

Khi bạn chèn một điểm ngắt, trình gỡ lỗi sẽ gán một số cho nó. Các số được gán cho các điểm ngắt là các số nguyên liên tiếp bắt đầu bằng số 1 mà bạn có thể tham khảo khi làm việc với các điểm ngắt.

Các điểm ngắt có thể được đặt ở một số dòng nhất định bằng cách thực hiện theo cú pháp của <program_file>:<line_number> như hình dưới đây:

(Pdb) break looping.py:5 Breakpoint 1 at /Users/sammy/looping.py:5 (Pdb) 

Nhập clear và sau đó y để xóa tất cả các điểm ngắt hiện tại. Sau đó, bạn có thể đặt một điểm ngắt nơi một hàm được xác định:

(Pdb) break looping.nested_loop Breakpoint 1 at /Users/sammy/looping.py:5 (Pdb)  

Để xóa các điểm ngắt hiện tại, hãy nhập clear , sau đó nhập y . Bạn cũng có thể cài đặt một điều kiện:

(Pdb) break looping.py:7, number > 500 Breakpoint 1 at /Users/sammy/looping.py:7 (Pdb)      

Bây giờ, nếu ta sử dụng lệnh continue , chương trình sẽ ngắt khi number x được đánh giá là lớn hơn 500 (nghĩa là khi nó được đặt bằng 600 trong lần lặp thứ hai của vòng lặp ngoài):

(Pdb) continue 500 x y z > /Users/sammy/looping.py(7)nested_loop() -> print(number) (Pdb)  

Để xem danh sách các điểm ngắt hiện được cài đặt để chạy, hãy sử dụng lệnh break mà không có bất kỳ đối số nào. Bạn sẽ nhận được thông tin về các đặc điểm của (các) điểm ngắt mà bạn đã đặt:

(Pdb) break Num Type         Disp Enb   Where 1   breakpoint   keep yes   at /Users/sammy/looping.py:7     stop only if number > 500     breakpoint already hit 2 times (Pdb)  

Ta cũng có thể vô hiệu hóa một điểm ngắt bằng lệnh disable và số lượng điểm ngắt. Trong phiên này, ta thêm một điểm ngắt khác và sau đó tắt điểm đầu tiên:

(Pdb) break looping.py:11 Breakpoint 2 at /Users/sammy/looping.py:11 (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/looping.py:7 (Pdb) break Num Type         Disp Enb   Where 1   breakpoint   keep no    at /Users/sammy/looping.py:7     stop only if number > 500     breakpoint already hit 2 times 2   breakpoint   keep yes   at /Users/sammy/looping.py:11 (Pdb)  

Để bật một điểm ngắt, hãy sử dụng lệnh enable và để loại bỏ hoàn toàn một điểm ngắt, hãy sử dụng lệnh clear :

(Pdb) enable 1 Enabled breakpoint 1 at /Users/sammy/looping.py:7 (Pdb) clear 2 Deleted breakpoint 2 at /Users/sammy/looping.py:11 (Pdb)  

Các điểm ngắt trong pdb cung cấp cho bạn rất nhiều quyền kiểm soát. Một số chức năng bổ sung bao gồm bỏ qua các điểm ngắt trong quá trình lặp lại hiện tại của chương trình bằng lệnh ignore (như trong ignore 1 ), kích hoạt các hành động xảy ra tại một điểm ngắt bằng commands lệnh (như trong command 1 ) và tạo các điểm ngắt tạm thời được xóa tự động lần đầu tiên việc thực thi chương trình đạt điểm bằng lệnh tbreak (ví dụ: đối với ngắt tạm thời ở dòng 3, bạn có thể nhập tbreak 3 ).

Tích hợp pdb vào chương trình

Bạn có thể kích hoạt phiên gỡ lỗi bằng lệnh module pdb và thêm hàm pdb pdb.set_trace() phía trên dòng nơi bạn muốn phiên bắt đầu.

Trong chương trình mẫu của ta ở trên, ta sẽ thêm câu lệnh import và hàm mà ta muốn nhập vào trình gỡ lỗi. Đối với ví dụ của ta , hãy thêm nó vào trước vòng lặp lồng nhau.

# Import pdb module import pdb  num_list = [500, 600, 700] alpha_list = ['x', 'y', 'z']   def nested_loop():     for number in num_list:         print(number)          # Trigger debugger at this line         pdb.set_trace()         for letter in alpha_list:             print(letter)  if __name__ == '__main__':     nested_loop()  

Bằng cách thêm trình gỡ lỗi vào mã của bạn, bạn không cần phải chạy chương trình của bạn theo cách đặc biệt hoặc nhớ đặt các điểm ngắt.

Nhập module pdb và chạy hàm pdb.set_trace() cho phép bạn bắt đầu chương trình của bạn như bình thường và chạy trình gỡ lỗi thông qua quá trình thực thi của nó.

Sửa đổi quy trình thực thi chương trình

Trình gỡ lỗi Python cho phép bạn thay đổi stream chương trình của bạn trong thời gian chạy bằng lệnh jump . Điều này cho phép bạn chuyển tiếp để ngăn một số mã chạy hoặc có thể cho phép bạn quay lại để chạy lại mã.

Ta sẽ làm việc với một chương trình nhỏ tạo danh sách các chữ cái có trong chuỗi sammy = "sammy" :

letter_list.py
def print_sammy():     sammy_list = []     sammy = "sammy"     for letter in sammy:         sammy_list.append(letter)         print(sammy_list)  if __name__ == "__main__":     print_sammy()  

Nếu ta chạy chương trình như bình thường với lệnh python letter_list.py , ta sẽ nhận được kết quả sau:

Output
['s'] ['s', 'a'] ['s', 'a', 'm'] ['s', 'a', 'm', 'm'] ['s', 'a', 'm', 'm', 'y']

Với trình gỡ lỗi Python, hãy chỉ ra cách ta có thể thay đổi quá trình thực thi bằng cách chuyển tiếp trước sau chu kỳ đầu tiên. Khi ta làm điều này, ta sẽ nhận thấy rằng có sự gián đoạn của vòng lặp for :

  • python -m pdb letter_list.py
> /Users/sammy/letter_list.py(1)<module>() -> def print_sammy(): (Pdb) list   1  -> def print_sammy():   2         sammy_list = []   3         sammy = "sammy"   4         for letter in sammy:   5             sammy_list.append(letter)   6             print(sammy_list)   7        8     if __name__ == "__main__":   9         print_sammy()  10       11      (Pdb) break 5 Breakpoint 1 at /Users/sammy/letter_list.py:5 (Pdb) continue > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) pp letter 's' (Pdb) continue ['s'] > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) jump 6 > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 'a' (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/letter_list.py:5 (Pdb) continue ['s'] ['s', 'm'] ['s', 'm', 'm'] ['s', 'm', 'm', 'y'] 

Phiên gỡ lỗi ở trên đặt ngắt ở dòng 5 để ngăn mã tiếp tục, sau đó tiếp tục thông qua mã (cùng với việc in khá đẹp một số giá trị của letter để hiển thị những gì đang xảy ra). Tiếp theo, ta sử dụng lệnh jump để bỏ qua dòng 6. Đến đây, letter biến được đặt bằng chuỗi 'a' , nhưng ta nhảy mã thêm mã đó vào danh sách sammy_list . Sau đó, ta vô hiệu hóa điểm ngắt để tiếp tục thực thi như bình thường với lệnh continue , vì vậy 'a' không bao giờ được thêm vào sammy_list .

Tiếp theo, ta có thể thoát khỏi phiên đầu tiên này và khởi động lại trình gỡ lỗi để quay trở lại bên trong chương trình để chạy lại một câu lệnh đã được thực thi. Lần này, ta sẽ chạy lại lần lặp đầu tiên của vòng lặp for trong trình gỡ lỗi:

> /Users/sammy/letter_list.py(1)<module>() -> def print_sammy(): (Pdb) list   1  -> def print_sammy():   2         sammy_list = []   3         sammy = "sammy"   4         for letter in sammy:   5             sammy_list.append(letter)   6             print(sammy_list)   7        8     if __name__ == "__main__":   9         print_sammy()  10       11      (Pdb) break 6 Breakpoint 1 at /Users/sammy/letter_list.py:6 (Pdb) continue > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 's' (Pdb) jump 5 > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) continue > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 's' (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/letter_list.py:6 (Pdb) continue ['s', 's'] ['s', 's', 'a'] ['s', 's', 'a', 'm'] ['s', 's', 'a', 'm', 'm'] ['s', 's', 'a', 'm', 'm', 'y'] 

Trong phiên gỡ lỗi ở trên, ta đã thêm dấu ngắt ở dòng 6, và sau đó quay trở lại dòng 5 sau khi tiếp tục. Ta đã in khá sammy_list để cho thấy rằng chuỗi 's' đã được thêm vào danh sách sammy_list hai lần. Sau đó, ta tắt ngắt ở dòng 6 và tiếp tục chạy chương trình. Kết quả hiển thị hai giá trị của 's' thêm vào sammy_list .

Một số bước nhảy được ngăn chặn bởi trình gỡ lỗi, đặc biệt khi nhảy vào và ra khỏi một số câu lệnh điều khiển stream nhất định không được xác định. Ví dụ, bạn không thể nhảy vào các hàm trước khi các đối số được xác định và bạn không thể nhảy vào giữa câu lệnh try:except . Bạn cũng không thể nhảy ra khỏi khối finally .

Câu lệnh jump với trình gỡ lỗi Python cho phép bạn thay đổi stream thực thi trong khi gỡ lỗi một chương trình để xem liệu điều khiển stream có thể được sửa đổi cho các mục đích khác nhau hay để hiểu rõ hơn vấn đề nào đang phát sinh trong mã của bạn.

Bảng các lệnh pdb phổ biến

Dưới đây là một bảng các lệnh pdb hữu ích cùng với các biểu mẫu ngắn của chúng mà bạn cần lưu ý khi làm việc với trình gỡ lỗi Python.

Chỉ huy Hình thức ngắn Những gì nó làm
args a In danh sách đối số của hàm hiện tại
break b Tạo điểm ngắt (yêu cầu tham số) trong quá trình thực thi chương trình
continue c hoặc cont Tiếp tục thực hiện chương trình
help h Cung cấp danh sách các lệnh hoặc trợ giúp cho một lệnh cụ thể
jump j Đặt dòng tiếp theo sẽ được thực thi
list l In mã nguồn xung quanh dòng hiện tại
next n Tiếp tục thực hiện cho đến khi đạt đến dòng tiếp theo trong hàm hiện tại hoặc trả về
step s Thực hiện dòng hiện tại, dừng lại ở lần đầu tiên có thể
pp pp In đẹp giá trị của biểu thức
quit hoặc exit q Hủy bỏ chương trình
return r Tiếp tục thực hiện cho đến khi hàm hiện tại trả về

Bạn có thể đọc thêm về các lệnh và cách làm việc với trình gỡ lỗi từ tài liệu trình gỡ lỗi Python .

Kết luận

Gỡ lỗi là một bước quan trọng của bất kỳ dự án phát triển phần mềm nào. Trình gỡ lỗi Python pdb triển khai một môi trường gỡ lỗi tương tác mà bạn có thể sử dụng với bất kỳ chương trình nào của bạn được viết bằng Python.

Với các tính năng cho phép bạn tạm dừng chương trình của bạn , hãy xem các giá trị mà các biến của bạn được đặt thành và thực hiện chương trình theo từng bước riêng biệt, bạn có thể hiểu đầy đủ hơn chương trình của bạn đang làm gì và tìm ra các lỗi tồn tại logic hoặc khắc phục sự cố đã biết.


Tags:

Các tin liên quan

Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên CentOS 7
2017-04-20
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Debian 8
2017-04-20
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên CentOS 7
2017-04-20
Cách áp dụng tính đa hình cho các lớp trong Python 3
2017-04-13
Hướng dẫn dự báo chuỗi thời gian với prophet bằng Python 3
2017-04-04
Hiểu các biến lớp và phiên bản trong Python 3
2017-03-27
Hướng dẫn Dự báo chuỗi thời gian với ARIMA bằng Python 3
2017-03-23
Cách tạo lớp và xác định đối tượng trong Python 3
2017-03-17
Hướng dẫn về image hóa chuỗi thời gian với Python 3
2017-03-14
Cách viết comment bằng Python 3
2017-03-03