Hướng dẫn python debug - gỡ lỗi python

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
4

Giới thiệu

Gỡ lỗi (Debugging) là một trong những điều kinh khủng, khó ưa nhất trong phát triển phần mềm, nhưng điều ngang trái thay nó lại là một trong những thứ quan trọng bậc nhất trong vòng đời phát triển phần mềm. Chắc chắn trong giai đoạn phát triển, mọi lập trình viên đều phải tự debug mã của mình, điều này là không thể tránh khỏi.

Có rất nhiều cách để debug một ứng dụng viết ra. Một phương pháp được sử dụng rất phổ biến đó là sử dụng câu lệnh "print" trong các trường hợp để xem nó chạy như thế nào trong khi thực thi chương trình. Tuy nhiên, phương pháp này xảy ra nhiều vấn đề, chẳng hạn như việc muốn in được các giá trị của biến thì phải thêm code vào, v.v.. => Quá phức tạp. Hơn nữa cách này chỉ dùng tạm bợ cho những chương trình nhỏ, tầm trăm dòng code trở lại thì ok, khi mà sang một chương trình lớn hơn, có nhiều file hơn, thì nó lại là một vấn đề lớn lớn lớn.

Vậy thì, chúng ta đã có trình gỡ lỗi để giải quyết vấn đề đó cho. Nó giúp chúng ta tìm các lỗi trong một ứng dụng bằng các lệnh bên ngoài, do đó không có thay đổi nào đối với code. Như đã đề cập ở trên, mình muốn giới thiệu các bạn module PDB - một module tích hợp bên trong python (không cần phải cài đặt từ nguồn bên ngoài), thông qua bài viết "Có PDB, debug python không còn khó khăn".

Mời các bạn đọc tiếp.

Các lệnh thao tác cơ bản

Để hiểu các lệnh hoặc công cụ chính có trong PDB, mình sẽ viết một đoạn chương trình nho nhỏ, vui vui, cơ bản, sau đó thử debug bằng các lệnh PDB. Bằng các này, chúng ta sẽ thấy được một cách rõ ràng hơn, chính xác hơn mỗi lệnh của PDB sẽ làm gì.

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
5

list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
love_codes = [1111112, 2111014, 3451524, 4512244]

def show_list_crush():
    print('List crush: ')
    for crush in list_crush:
        print(crush)

    print("Code of loves: ")
    for code in love_codes:
        print(code)

def main():
    show_list_crush()

if __name__ == "__main__":
    main()

Kết quả của đoạn script trên:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244

Đoạn mã trên chắc mình cũng không cần phải giải thích nhiều, vì nó là basic đối với một người làm về python rồi, nó không có gì khó cũng như cú pháp phức tạp cả. Bạn không cần phải hiểu đoạn lệnh trên thực hiện, mục đích chính của mình thực hiện một số lệnh PDB dựa trên chương trình này. Ok, bắt đầu thôi.

Sử dụng PDB yêu cầu sử dụng Command Line Interface (CLI), do đó bạn phải chạy ứng dụng của mình từ

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
6 hoặc
> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
7.

Chạy lệnh dưới đây trong CLI của bạn:

python -m pdb loves.py

Trong lệnh trên, tên tệp

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
8, vì vậy bạn sẽ cần chèn tên tệp của bạn thay cho
> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
8.

Lưu ý:

python -m pdb loves.py var1 var2 var3
0 là một cờ và nó thông báo cho Python rằng một mô-đun cần phải được imported; cờ này được theo sau bởi tên của mô-đun, trong trường hợp của trên của mình là pdb.
python -m pdb loves.py var1 var2 var3
0 là một cờ và nó thông báo cho Python rằng một mô-đun cần phải được imported; cờ này được theo sau bởi tên của mô-đun, trong trường hợp của trên của mình là pdb.

Chạy xong lệnh sẽ hiển thị như thế này:

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)

Đầu ra sẽ luôn có cùng cấu trúc. Nó sẽ bắt đầu với đường dẫn thư mục đến tệp mã nguồn. Sau đó, trong ngoặc, nó sẽ cho biết số dòng từ tệp mà PDB hiện đang trỏ tới, trong trường hợp của mình là

python -m pdb loves.py var1 var2 var3
1. Dòng tiếp theo, bắt đầu bằng ký hiệu
python -m pdb loves.py var1 var2 var3
2, cho biết dòng đang được trỏ đến.

Để đóng PDB, chỉ cần nhập

python -m pdb loves.py var1 var2 var3
3 hoặc
python -m pdb loves.py var1 var2 var3
4.

Một vài điều cần lưu ý, nếu chương trình của bạn có các tham số đầu vào, bạn cũng có thể chuyển chúng qua command line. Ví dụ, nếu chương trình của mình yêu cầu 3 đầu vào từ người dùng, thì lệnh của mình sẽ như thế này:

python -m pdb loves.py var1 var2 var3

Tiếp tục, nếu trước đó bạn đã đóng PDB thông qua lệnh

python -m pdb loves.py var1 var2 var3
3 hoặc
python -m pdb loves.py var1 var2 var3
4, sau đó chạy lại tệp code thông qua PDB. Sau đó, chạy lệnh sau trong command line PDB:

(Pdb) list

Đầu ra trông như thế này:

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)

Hiển thị 11 dòng đầu tiên của chương trình cho bạn, với dấu

python -m pdb loves.py var1 var2 var3
2 hướng về dòng hiện tại đang được debug. Tiếp theo, hãy thử lệnh này trong command line PDB.

(Pdb) list 4, 6

Lệnh này sẽ chỉ hiển thị các dòng được chọn, trong trường hợp này là các dòng 4 đến 6. Đây là đầu ra:

  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
(Pdb)

Debugging với Break points

Một thứ quan trọng tiếp theo mà chúng ta sẽ tìm hiểu là

python -m pdb loves.py var1 var2 var3
8.
python -m pdb loves.py var1 var2 var3
8 thường được sử dụng cho các chương trình lớn hơn, nhưng để hiểu rõ hơn về nó, chúng ta sẽ tìm hiểu cách nó hoạt động dựa trên ví dụ cơ bản bên trên.
python -m pdb loves.py var1 var2 var3
8 là các vị trí cụ thể mà chúng tôi khai báo trong code của mình. Code của tôi chạy đến vị trí đó và sau đó tạm dừng. Những điểm này được tự động gán số bởi PDB.

Có các tùy chọn sau đây để tạo

python -m pdb loves.py var1 var2 var3
8:

  1. Theo số dòng (By line number)
  2. Bằng cách khai báo hàm (By function declaration)
  3. Bởi một điều kiện (By a condition)

Để khai báo

python -m pdb loves.py var1 var2 var3
8 theo số dòng, hãy chạy lệnh sau trong commandline PDB:

(Pdb) break loves.py:9

Lệnh này chèn một

python -m pdb loves.py var1 var2 var3
8 ở dòng code thứ 8, nó sẽ tạm dừng chương trình một khi nó chạy tới điểm đó. Đầu ra lệnh này được hiển thị là:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
0

Để khai báo các

python -m pdb loves.py var1 var2 var3
8 trên một hàm, hãy chạy lệnh sau:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
1

Để chèn một điểm dừng theo cách này, bạn phải khai báo nó bằng tên tệp và sau đó là tên hàm. Điều này xuất ra như sau:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
2

Như bạn thấy,

python -m pdb loves.py var1 var2 var3
8 này đã được được tự động gán số 2 và số dòng là 4 - chính là tại dòng hàm được khai báo.

python -m pdb loves.py var1 var2 var3
8 cũng có thể được khai báo bởi một điều kiện. Trong trường hợp đó, chương trình sẽ chạy cho đến khi điều kiện sai và sẽ tạm dừng khi điều kiện đó trở thành đúng. Chạy lệnh sau:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
3

Điều này sẽ theo dõi giá trị của biến

(Pdb) list
7 trong suốt quá trình thực thi và chỉ ngắt khi giá trị của nó là "Thi" ở dòng 6.

Để xem tất cả các

python -m pdb loves.py var1 var2 var3
8 mà mình đã khai báo dưới dạng danh sách, hãy chạy lệnh

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
4

Kết quả sẽ có dạng:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
5

Cuối cùng, làm thế nào chúng ta có thể vô hiệu hóa, kích hoạt và xóa một

python -m pdb loves.py var1 var2 var3
8 cụ thể tại bất kỳ trường hợp nào. Chạy lệnh sau:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
6

Lệnh trên sẽ vô hiệu hóa

python -m pdb loves.py var1 var2 var3
8 2, nhưng sẽ không xóa nó khỏi phiên debug.

Bạn sẽ thấy số

python -m pdb loves.py var1 var2 var3
8 bị vô hiệu hóa.

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
7

Cho phép xem lại danh sách tất cả các

python -m pdb loves.py var1 var2 var3
8 để xem giá trị "End" cho
python -m pdb loves.py var1 var2 var3
8 2:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
4

Đầu ra:

List crush:
Lê
Thi
Thiên
Mỹ
code of loves:
1111112
2111014
3451524
4512244
9

Để kích hoạt

python -m pdb loves.py var1 var2 var3
8 2:

python -m pdb loves.py
0

Và một lần nữa, đây là đầu ra:

python -m pdb loves.py
1

Bây giờ, nếu bạn in danh sách tất cả các điểm break, giá trị "End" của breakpoint 2 sẽ hiển thị

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
5.

Bây giờ chúng ta hãy xóa breakpoint 1:

python -m pdb loves.py
2

Kết quả:

python -m pdb loves.py
3

Nếu chúng ta in lại danh sách các breakpoint, thì bây giờ sẽ chỉ hiển thị 2 breakpoint. Hãy kiểm tra bằng lệnh

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
6.

python -m pdb loves.py
4

Đúng như mong đợi.

Trước khi làm điều tiếp theo, tôi muốn hiển thị ra kết quả khi chạy code cho đến khi breakpoint được đặt. Để làm điều đó, hãy xóa tất cả các breakpoint trước đó và khai báo một breakpoint khác thông qua commandline PDB:

  1. Xóa tất cả các breakpoint

    python -m pdb loves.py
    
    5

    Sau đó, nhập "y" và nhấn "ENter". Bạn sẽ thấy kết quả xuất hiện:

    python -m pdb loves.py
    
    6
  2. Khai báo một breakpoint mới

    Mình sẽ chạy cho đến khi giá trị của biến

    (Pdb) list
    
    7 bằng "Thi". Vì vậy, về cơ bản, chương trình sẽ tạm dừng trước chữ "Lê".

    List crush:
    Lê
    Thi
    Thiên
    Mỹ
    code of loves:
    1111112
    2111014
    3451524
    4512244
    
    3
  3. Chạy cho đến breakpoint

Để chạy code, sử dụng

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
8, lệnh này sẽ thực thi code cho đến khi chạm breakpoint hoặc kết thúc:

python -m pdb loves.py
8

Bạn sẽ thấy:

python -m pdb loves.py
9

Chương trình chạy cho đến breakpoint và tạm dừng, bây giờ tùy thuộc vào chúng ta muốn thay đổi bất cứ điều gì, kiểm tra các kiến hoặc nếu chúng ta muốn chạy tập lệnh cho đến khi hết hoàn thành. Hãy chạy lệnh

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
8.

Kết quả:

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
0

Nếu chúng ta in lại danh sách các breakpoint, thì bây giờ sẽ chỉ hiển thị 2 breakpoint. Hãy kiểm tra bằng lệnh

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
6.

Đúng như mong đợi. Trước khi di chuyển về phía trước, hãy xóa tất cả các breakpoint bằng cách chạy lệnh

(Pdb) list 4, 6
0, sau đó nhập
(Pdb) list 4, 6
1 vào commandline PDB.

Trước khi làm điều tiếp theo, tôi muốn hiển thị ra kết quả khi chạy code cho đến khi breakpoint được đặt. Để làm điều đó, hãy xóa tất cả các breakpoint trước đó và khai báo một breakpoint khác thông qua commandline PDB:

Xóa tất cả các breakpoint

Sau đó, nhập "y" và nhấn "ENter". Bạn sẽ thấy kết quả xuất hiện:

Khai báo một breakpoint mới

Mình sẽ chạy cho đến khi giá trị của biến

(Pdb) list
7 bằng "Thi". Vì vậy, về cơ bản, chương trình sẽ tạm dừng trước chữ "Lê".

python -m pdb loves.py

Chạy cho đến breakpoint

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
2

Để chạy code, sử dụng

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
8, lệnh này sẽ thực thi code cho đến khi chạm breakpoint hoặc kết thúc:

> /home/nguyenmanh/projects/test/loves.py(1)<module>()
-> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
(Pdb)
3

Bạn sẽ thấy:

Chương trình chạy cho đến breakpoint và tạm dừng, bây giờ tùy thuộc vào chúng ta muốn thay đổi bất cứ điều gì, kiểm tra các kiến hoặc nếu chúng ta muốn chạy tập lệnh cho đến khi hết hoàn thành. Hãy chạy lệnh

  1  -> list_crush = ['Lê', 'Thi', 'Thiên', 'Mỹ']
  2     love_codes = [1111112, 2111014, 3451524, 4512244]
  3  
  4     def show_list_crush():
  5         print('List crush: ')
  6         for crush in list_crush:
  7             print(crush)
  8  
  9         print("code of loves: ")
 10         for code in love_codes:
 11             print(code)
(Pdb)
8.

Trong kết quả trên, có thể thấy chương trình tiếp tục từ breakpoint, chạy phần còn lại và sau đó khởi động lại để cho phép chúng ta debug thêm nếu muốn. Bây giờ chuyển sang phần tiếp nha.

Lưu ý quan trọng: Trước khi di chuyển về phía trước, hãy xóa tất cả các breakpoint bằng cách chạy lệnh

(Pdb) list 4, 6
0, sau đó nhập
(Pdb) list 4, 6
1 vào commandline PDB.

Chức năng Next và Step