2.41. Налагодження¶
Більшість скриптів, що не працюють на камері, зазнають невдачі одним із трьох способів: вони генерують виняток, повертають неправильне значення або зависають. Для кожного випадку є окремий набір інструментів.
2.41.1. Читання трасування¶
Коли скрипт генерує виняток і нічого його не перехоплює, REPL або IDE виводить трасування – запис ланцюга викликів від зовнішнього скрипту до рядка, де виник виняток.
Трасування читається знизу вгору:
Нижній рядок називає клас винятку та його повідомлення (
ValueError: invalid literal for int()...).Кожен блок
File "...", line N, in <name>вище – це кадр – один виклик глибше, якщо рухатися вгору.Найвищий кадр – це місце, де скрипт почався; найнижчий кадр – де виникла помилка.
Спочатку прочитайте нижній рядок, щоб дізнатися що пішло не так, а потім рухайтеся вгору, щоб побачити як скрипт до цього дійшов. Номери рядків вказують на точні місця у вихідному коді скрипту.
2.41.2. Налагодження за допомогою print¶
Найшвидший спосіб дізнатися, що робить скрипт, – вивести підозрілі значення. Три вбудовані функції роблять виведення кориснішим:
repr()– повертає рядок у стилі розробника для значення.print(repr(value))розрізняє"5"від5іNoneвід"None", що звичайнийprint()не може.type()– повертає клас значення.print(type(value))допомагає з’ясувати, чи змінна, яка «має бути цілим числом», насправді є рядком.len()– довжина послідовності або колекції. Дивовижно великий відсоток помилок пов’язаний із розбіжностями на одиницю або неправильними розмірами.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Додайте print у кожну гілку, яка вас цікавить – обидві гілки if, кожен блок except, тіло циклу, який, як ви підозрюєте, не виконується жодного разу. Ціна – рядок виведення; цінність – з’ясування, чи виконується той шлях у коді, який ви вважаєте активним.
2.41.3. Дослідження об’єкта¶
Дві вбудовані функції відповідають на питання «що я можу зробити з цим?»:
dir()– повертає список усіх імен, визначених для об’єкта: методи, атрибути, спеціальні методи тощо.help()– виводить рядок документації (а в CPython – і сигнатуру) функції, методу або класу.
Використовуйте їх разом: dir знаходить ім’я, help пояснює, що воно робить.
2.41.3.1. Пошук імені за допомогою dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
Перша частина списку – це спеціальні методи, успадковані від кожного об’єкта; корисні для перегляду імена зазвичай знаходяться після них. dir працює з будь-чим – класом, екземпляром, модулем, вбудованим типом:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Другий варіант – це спосіб дізнатися, які імена верхнього рівня насправді надає модуль, не виходячи з REPL.
2.41.3.2. Пошук за допомогою help¶
Коли dir знайшов кандидата, help його описує:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
У MicroPython help лаконічніший, ніж у CPython – іноді лише сигнатура, іноді однорядковий рядок документації, а іноді нічого для вбудованих функцій на C. Він все одно є зручним нагадуванням, коли підказка IDE недоступна.
2.41.4. Коли щось зависає¶
Скрипт, що не повертає керування, складніше діагностувати, ніж той, що генерує виняток. Поширені причини:
Цикл
while, умова якого ніколи не стає хибною. Додайте виведення змінної циклу на кожній ітерації; якщо значення не змінюється, є помилка в тілі циклу.Блокуючий виклик, що очікує введення, яке ніколи не надходить – читання з порожньої черги, нескінченне очікування. Огорніть виклик операціями print, щоб побачити, на якому рядку скрипт застряг.
Нескінченна рекурсія. Трасування, яке врешті-решт виникне (з
RecursionError), зазвичай відразу вказує на неї.
Найефективніший спосіб відновлення після зависання скрипту – кнопка stop в IDE, яка надсилає KeyboardInterrupt до скрипту через USB. Переривання з’являється як трасування на рядку, що виконується в цей момент – часто саме на тому рядку, який не повертає керування.
Примітка
Якщо зависання не піддається жодній діагностиці – скрипт виглядає правильно, трасування переривання вказує на вбудований код або код мікропрограми замість вашого скрипту, або той самий код працював у попередній версії мікропрограми – причиною може бути помилка мікропрограми, а не скрипту. Зведіть скрипт до найменшого відтворюваного прикладу, що все одно зависає, і опублікуйте звіт на форумі OpenMV. Вкажіть версію мікропрограми, плату та зведений скрипт.
2.41.5. Приберіть діагностику перед розгортанням¶
Стратегічне використання print під час розробки чудово виправдовує себе; але сотня викликів print, залишена у виробничому скрипті, засмічує виведення і витрачає купу пам’яті, яку могла б використовувати основна робота. Коли помилку виправлено, приберіть print-виклики (або захистіть їх за прапором налагодження, який можна вимкнути).
Для діагностики, яка має залишатися в коді надовго, перейдіть від print() до модуля logging. Він прив’язує рівень до кожного повідомлення (debug, info, warning, error) і дозволяє одним налаштуванням заглушити тихі повідомлення у виробничому середовищі:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Встановлення рівня реєстратора на logging.WARNING робить виклики info і debug практично безвитратними (рядок повідомлення ніколи не формується), без необхідності коментувати рядки. Це робить logging правильним інструментом для постійної діагностики; звичайний print добре підходить для тимчасової.