2.41. Gỡ lỗi¶
Hầu hết các tập lệnh thất bại trên camera đều thất bại theo một trong ba cách: chúng gây ra ngoại lệ, chúng tạo ra giá trị sai, hoặc chúng bị treo. Mỗi cách có một bộ công cụ khác nhau.
2.41.1. Đọc traceback¶
Khi một tập lệnh gây ra ngoại lệ và không có gì xử lý ngoại lệ đó, REPL hoặc IDE sẽ in ra một traceback -- bản ghi chuỗi lời gọi từ tập lệnh ngoài cùng xuống đến dòng đã gây ra lỗi.
Một traceback được đọc từ dưới lên trên:
Dòng dưới cùng đặt tên cho lớp ngoại lệ và thông báo của nó (
ValueError: invalid literal for int()...).Mỗi khối
File "...", line N, in <name>phía trên là một khung hình -- một lời gọi sâu hơn khi bạn đi lên trên.Khung hình trên cùng là nơi tập lệnh bắt đầu; khung hình dưới cùng là nơi lỗi xuất hiện.
Đọc từ dưới trước để biết điều gì đã xảy ra, sau đó đi lên để xem làm thế nào tập lệnh đến đó. Các số dòng trỏ đến vị trí nguồn chính xác trong tập lệnh.
2.41.2. Gỡ lỗi bằng Print¶
Cách nhanh nhất để tìm hiểu một tập lệnh đang làm gì là in ra các giá trị nghi ngờ. Ba hàm tích hợp sẵn giúp lệnh print hữu ích hơn:
repr()-- trả về chuỗi kiểu nhà phát triển cho một giá trị.print(repr(value))phân biệt"5"với5vàNonevới"None", điều màprint()thông thường không thể làm.type()-- trả về lớp của một giá trị.print(type(value))là cách để tìm hiểu xem biến "nên là int" có bí mật là một chuỗi không.len()-- độ dài của một chuỗi hoặc tập hợp. Một phần lớn đáng ngạc nhiên của các lỗi là vấn đề lệch một phần tử hoặc không khớp kích thước.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Đặt lệnh print bên trong mỗi nhánh bạn quan tâm -- cả hai nhánh của if, mỗi khối except, phần thân của vòng lặp bạn nghi ngờ chạy không lần nào. Chi phí là một dòng đầu ra; giá trị là tìm hiểu xem đường dẫn mã bạn nghĩ đang chạy có thực sự đang chạy không.
2.41.3. Khám phá một đối tượng¶
Hai hàm tích hợp sẵn trả lời "tôi có thể làm gì với thứ này":
dir()-- trả về danh sách mọi tên được định nghĩa trên một đối tượng: phương thức, thuộc tính, dunder, tất cả.help()-- in ra docstring (và trên CPython, chữ ký) của một hàm, phương thức, hoặc lớp.
Sử dụng chúng cùng nhau: dir tìm tên, help giải thích nó làm gì.
2.41.3.1. Tìm tên với dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
Phần đầu tiên của danh sách là các phương thức dunder, được kế thừa từ mọi đối tượng; các tên đáng quét thường nằm sau chúng. dir hoạt động trên bất cứ thứ gì -- một lớp, một thể hiện, một module, một kiểu tích hợp sẵn:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Dạng thứ hai là cách tìm hiểu những tên cấp cao nhất nào mà một module thực sự cung cấp mà không cần rời khỏi REPL.
2.41.3.2. Tra cứu với help¶
Khi dir đã tìm thấy một ứng viên, help mô tả nó:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
Trên MicroPython, help ít chi tiết hơn so với CPython -- đôi khi chỉ là chữ ký, đôi khi là một dòng docstring, đôi khi không có gì với các hàm C tích hợp sẵn. Nó vẫn là một lời nhắc nhanh khi tooltip IDE không ở gần.
2.41.4. Khi có sự cố treo¶
Một tập lệnh không trả về khó chẩn đoán hơn một tập lệnh gây ra ngoại lệ. Các nguyên nhân thường gặp:
Một vòng lặp
whilecó điều kiện không bao giờ trở thành false. Thêm lệnh print về biến vòng lặp mỗi lần lặp; nếu giá trị không thay đổi, phần thân vòng lặp có lỗi.Một lời gọi chặn đang chờ đầu vào không bao giờ đến -- đọc từ hàng đợi rỗng, ngủ không có hồi kết. Đặt lệnh print xung quanh lời gọi để xem tập lệnh đang bị kẹt ở dòng nào.
Đệ quy vô hạn. Traceback khi nó cuối cùng xuất hiện (với
RecursionError) thường trỏ ngay vào đó.
Cách phục hồi hiệu quả nhất cho một tập lệnh bị treo là nút stop của IDE, gửi KeyboardInterrupt đến tập lệnh qua USB. Ngắt xuất hiện như một traceback tại dòng đang chạy -- thường là dòng chính xác không trả về.
Ghi chú
Nếu sự cố treo không chịu bất kỳ chẩn đoán nào -- tập lệnh có vẻ đúng, traceback ngắt trỏ vào một hàm tích hợp sẵn hoặc vào mã firmware thay vì tập lệnh của bạn, hoặc cùng một mã đã hoạt động trên bản firmware trước -- nguyên nhân có thể là lỗi firmware thay vì lỗi tập lệnh. Thu nhỏ tập lệnh xuống bộ tái hiện nhỏ nhất vẫn bị treo và mở báo cáo trên OpenMV forum. Bao gồm phiên bản firmware, board đã chạy, và tập lệnh rút gọn.
2.41.5. Loại bỏ các chẩn đoán trước khi phát hành¶
Các lệnh print chiến lược trong quá trình phát triển rất tuyệt; hàng trăm lời gọi print còn lại trong một tập lệnh sản xuất làm lộn xộn đầu ra và sử dụng heap mà công việc thực sự có thể sử dụng. Khi một lỗi được sửa, hãy loại bỏ các lệnh print (hoặc đặt chúng sau một cờ debug bạn có thể tắt).
Đối với các chẩn đoán nên tồn tại lâu dài trong đường dẫn mã, hãy chuyển từ print() sang module logging. Nó gắn một mức độ vào mỗi thông báo (debug, info, warning, error) và cho phép một cài đặt duy nhất tắt tiếng các thông báo ít quan trọng trong môi trường sản xuất:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Đặt mức độ của logger thành logging.WARNING làm cho các lời gọi info và debug về cơ bản không tốn kém (chuỗi thông báo không bao giờ được xây dựng), mà không cần chú thích các dòng ra. Điều đó làm cho logging là công cụ phù hợp cho các chẩn đoán lâu dài; print thô là tốt cho các chẩn đoán dùng xong bỏ.