В предыдущей статье мы закончили разговор о профилировании обзором событийных профайлеров.
Сегодня я предлагаю рассмотреть методы отладки программ.
Прежде, чем начинать разговор об отладке, нам нужно определиться, что же это такое. По традиции, обратимся к Википедии: «Отладка — обнаружение, локализация и устранение ошибок». Вообще, для успешной отладки программы, нам необходимо (но не всегда достаточно) две вещи: значение переменных в том месте программы, в котором произошла ошибка, а так же стектрейс: порядок вызова функций, вплоть до ошибочной. Полезно так же знать об окружении, в котором выполняется программа: наличие (или отсутствие) свободной памяти, системные ограничения (например, на количество файловых дескрипторов) и так далее, но это слегка выходит за пределы нашей статьи.
Что делает начинающий программист, когда хочет «проникнуть» внутрь программы и изучить содержимое переменных (в нужных местах) и логику работы программы (вызовы функций, выполнение условий)? Он прибегает к самому распространённому, самому простому и самому «действенному» способу отладки: расставляет по всему коду «принты» (оператор print в Python 2.x и функция print() в Python 3.x). Но не только начинающие грешат этим: продвинутые разработчики частенько ленятся использовать специальные инструменты для отладки, надеясь быстренько, за пару минут, найти причину ошибки и всё исправить, и не замечая, что поиски ошибок растягиваются на часы и даже дни. Кстати, такой подход называют «журналированием».
Сюда же относится запись всей нужной для отладки программы информации в лог-файл. Иногда других вариантов нет, например, когда скрипт работает в продакшене и ошибки возникают эпизодически, или когда ошибка проявляется только после долгой работы программы (скажем, через две-три недели после запуска).
Но мы же пишем на Python, так почему бы не воспользоваться встроенными средствами отладки или инструментами, предлагаемыми сообществом? Например, вместо обычного логгирования в файл имеет смысл использовать Sentry, чтобы кроме сообщения об ошибке можно было отправить дополнительную информацию: стектрейс со всеми локальными переменными, любые другие переменные и вообще всё, что посчитаете нужным.
Python имеет встроенный отладчик: модуль pdb. В принципе, на этом можно было бы закончить статью, т.к. pdb — чрезвычайно мощный отладчик и всё остальное, по сути, всего лишь «украшательства» к нему. Но мы продолжим =)
Как можно заметить из названия, pdb многое взял от gdb (GNU Project debugger) — отладчика Си (и не только) программ. К слову, программы на Python можно отлаживать и с помощью gdb, просто это немножко сложнее и дольше, хотя тем, кто хочет углубиться в устройство языка, а так же тем, кто пишет сишные модули для питона без gdb никак не обойтись. Кроме того, gdb позволяет подключиться к уже работающей программе (через её pid) и заниматься её отладкой «прямо на месте».
Я сейчас не буду подробно описывать pdb, вот замечательная статья Дага Хеллманна (Doug Hellmann): pdb – Interactive Debugger на очень полезном ресурсе Python Module of the Week, а вот её хороший перевод на хабре: pdb – Интерактивный отладчик, выполненный пользователем xa4a.
Предлагаю сразу поставить IPython и модуль ipdb для него:
IPython (и ipdb, как его часть) предлагает «более лучшую» консоль и, как следствие, более удобную отладку программ: подсветка кода, автодополнение, историю команд, динамическую интроспекцию любых объектов, магические функции, алиасы и многое другое. Полный список улучшений можно посмотреть в документации или прямо из консоли IPython, введя "?" в качестве команды. Всё это помогает при отладке и делает её простой и увлекательной.
Запустить отладку скрипта в ipdb можно несколькими способами:
Эти команды откроют программу в отладчике, дальше можно делать всё, что угодно: ставить брейкпоинты, изучать её работу по шагам или просто запустить программу — отладчик автоматически остановится при возникновении неотловленного исключения.
Но обычно такой вариант чересчур изнурителен: пока доберёшься до нужного места всеми этими «next», «step», да и ставить точку останова («break») руками каждый раз утомительно. Гораздо удобнее в нужном месте программы вставить следующую строку:
И тогда при исполнении этой строки выполнение программы приостановится и будет запущен отладчик, — дальше можно начинать углубляться в изучение программы. По сути, функция «set_trace» — это установка точки останова (breakpoint).
Python Debugger имеет ещё один режим работы, который в некоторых случаях оказывается удобнее фукнции set_trace. Он называется «post mortem»: запуск отладчика с заданным трейсбеком:
или так:
В случае возникновения любых неотлавливаемых исключений в функции «some_code» будет вызван отладчик в том месте программы, где произошло исключение.
Интересной заменой функции «set_trace» является модуль debug, который просто импортирует библиотеки ipdb и see (удобная альтернатива функции dir) и запускает отладку. Единственным плюсом модуля является удобство использования, достаточно в любом месте программы вставить строку:
И при выполнении этой строки будет вызван отладчик «ipdb» и импортирован модуль «see».
Ещё одна интересная и, на этот раз, полезная библиотека: nose-ipdb. С её помощью можно автоматически запускать отладчик при ошибках (исключениях) или же просто при неверно отработанных тестах (я надеюсь, вы используете nose? =). Для запуска отладчика ipdb при ошибках, достаточно при запуске тестов добавить ключ "--ipdb":
А для запуска отладчика при некорректно завершившихся тестов нужно добавить ключ "--ipdb-failures":
Конечно, можно ловить всё и сразу:
Я каждый день использую этот модуль и просто не представляю себе жизни без него.
Потрясающий проект Армина Ронахера (Armin Ronacher), автора фреймворка Flask и вообще одного из крутейших программистов Python называется werkzeug и представляет собой сборник различных утилит для WSGI приложений. Одна из них — клёвый отладчик, который позволяет выводить удобный трейсбек ошибки, а так же запускать консоль Python в соответствующем месте трейсбека прямо на странице браузера:
Использовать его очень просто, достаточно обернуть приложение с помощью соответствующего middleware:
Говорят, что werkzeug умеет отлаживать даже Ajax-запросы, но, к сожалению, я сам лично никогда этого не делал. Буду рад любым комментариям на эту тему.
Ещё один хороший модуль, на этот раз для Django: django-pdb. Он позволяет запускать отладчик при наличии соответствующего GET-параметра в запросе (например: http://127.0.0.1:8000/app/view?ipdb) либо для всех запросов:
Либо вызывать отладчик при возникновении исключений (режим «post-mortem»):
или
в settings.py.
Но гораздо лучше в Django использовать модуль django-extensions, который добавляет очень полезную команду runserver_plus. С помощью этой батарейки можно подружить Django и Werkzeug (см. выше) и начать получать удовольствие от страниц с пятисотой ошибкой =)
Для использования всего этого чуда достаточно запустить девелоперский сервер с помощью команды runserver_plus:
Отладка программы в девелоперском окружении это, конечно, удобно и хорошо, но самое сложное — локализовать проблему по багрепорту от живого пользователя. Иногда это бывает сложно. Несмотря на все преимущества Python, модуля «телепатии» не существует, и разработчик остаётся один на один со словами пользователя «ничего не работает!!!11».
Проект Sentry позволяет сохранять каждую ошибку пользователя с текстом исключения, полным стектрейсом исключения и значениями всех локальных переменных в каждой из функций стектрейса, а так же много другой информации: окружение пользователя (браузер, ОС), все заголовки запроса и вообще всё, что пожелает разработчик.
Одинаковые ошибки группируются, таким образом можно наблюдать за «пульсом» проекта и чинить в первую очередь самые критичные места. Ещё один пример использования sentry — логгирование. Можно просто добавить в спорное место запись в лог сообщения с любыми интересующими разработчика переменными, и всё это окажется в sentry.
Но самый большой плюс в том, что всё это можно (и нужно!) использовать в продакшене.
Ещё один интересный отладчик: PuDB представляет собой консольный дебагер с графическим интерфейсом:
Не буду много о нём писать (честно говоря, я сам активно им не пользовался), предлагаю прочитать короткую заметка на хабре: Удобный отладчик для Python/Django проектов от пользователя int22h или более полный обзор: Отладка Python/Django при помощи PuDB.
Standalone отладчик Python, на этот раз с полноценным графическим интерфейсом: Winpdb:
Его разработчики утверждают, что winpdb в 20 раз быстрее pdb, а так же поддерживает работу с тредами. Очень большой и подробный туториал можно найти на этой странице: code.google.com/p/winpdb/wiki/DebuggingTutorial.
Отдельного слова заслуживают универсальные «комбайны» программирования: IDE, которые позволяют не выходя из редактора запустить код, отпрофилировать его или запустить встроенный отладчик. Я, пожалуй, выделю несколько продуктов, предназначенных для разработки на Python: PyCharm, PyDev, Wing IDE и PTVS.
Если честно, я не вижу особого смысла рассматривать каждую из этих IDE, достаточно знать что они есть, что они успешно справляются со своими задачами и вы можете использовать встроенный отладчик прямо из редактора, — это действительно удобно, круто и здорово.
Спасибо всем, кто дочитал и прокомментировал.
Владимир Рудных,
Технический руководитель Календаря Mail.Ru.
Сегодня я предлагаю рассмотреть методы отладки программ.
- Введение и теория — зачем вообще нужно профилирование, различные подходы, инструменты и отличия между ними
- Ручное и статистическое профилирование — переходим к практике
- Событийное профилирование — инструменты и их применение
- Отладка — что делать, когда ничего не работает
Прежде, чем начинать разговор об отладке, нам нужно определиться, что же это такое. По традиции, обратимся к Википедии: «Отладка — обнаружение, локализация и устранение ошибок». Вообще, для успешной отладки программы, нам необходимо (но не всегда достаточно) две вещи: значение переменных в том месте программы, в котором произошла ошибка, а так же стектрейс: порядок вызова функций, вплоть до ошибочной. Полезно так же знать об окружении, в котором выполняется программа: наличие (или отсутствие) свободной памяти, системные ограничения (например, на количество файловых дескрипторов) и так далее, но это слегка выходит за пределы нашей статьи.
Классика жанра
Что делает начинающий программист, когда хочет «проникнуть» внутрь программы и изучить содержимое переменных (в нужных местах) и логику работы программы (вызовы функций, выполнение условий)? Он прибегает к самому распространённому, самому простому и самому «действенному» способу отладки: расставляет по всему коду «принты» (оператор print в Python 2.x и функция print() в Python 3.x). Но не только начинающие грешат этим: продвинутые разработчики частенько ленятся использовать специальные инструменты для отладки, надеясь быстренько, за пару минут, найти причину ошибки и всё исправить, и не замечая, что поиски ошибок растягиваются на часы и даже дни. Кстати, такой подход называют «журналированием».
Сюда же относится запись всей нужной для отладки программы информации в лог-файл. Иногда других вариантов нет, например, когда скрипт работает в продакшене и ошибки возникают эпизодически, или когда ошибка проявляется только после долгой работы программы (скажем, через две-три недели после запуска).
Но мы же пишем на Python, так почему бы не воспользоваться встроенными средствами отладки или инструментами, предлагаемыми сообществом? Например, вместо обычного логгирования в файл имеет смысл использовать Sentry, чтобы кроме сообщения об ошибке можно было отправить дополнительную информацию: стектрейс со всеми локальными переменными, любые другие переменные и вообще всё, что посчитаете нужным.
The Python Debugger
Python имеет встроенный отладчик: модуль pdb. В принципе, на этом можно было бы закончить статью, т.к. pdb — чрезвычайно мощный отладчик и всё остальное, по сути, всего лишь «украшательства» к нему. Но мы продолжим =)
Как можно заметить из названия, pdb многое взял от gdb (GNU Project debugger) — отладчика Си (и не только) программ. К слову, программы на Python можно отлаживать и с помощью gdb, просто это немножко сложнее и дольше, хотя тем, кто хочет углубиться в устройство языка, а так же тем, кто пишет сишные модули для питона без gdb никак не обойтись. Кроме того, gdb позволяет подключиться к уже работающей программе (через её pid) и заниматься её отладкой «прямо на месте».
Я сейчас не буду подробно описывать pdb, вот замечательная статья Дага Хеллманна (Doug Hellmann): pdb – Interactive Debugger на очень полезном ресурсе Python Module of the Week, а вот её хороший перевод на хабре: pdb – Интерактивный отладчик, выполненный пользователем xa4a.
IPython pdb
Предлагаю сразу поставить IPython и модуль ipdb для него:
➜ pip install ipython ipdb
IPython (и ipdb, как его часть) предлагает «более лучшую» консоль и, как следствие, более удобную отладку программ: подсветка кода, автодополнение, историю команд, динамическую интроспекцию любых объектов, магические функции, алиасы и многое другое. Полный список улучшений можно посмотреть в документации или прямо из консоли IPython, введя "?" в качестве команды. Всё это помогает при отладке и делает её простой и увлекательной.
Запустить отладку скрипта в ipdb можно несколькими способами:
➜ python -m ipdb script.py
➜ ipdb script.py
Эти команды откроют программу в отладчике, дальше можно делать всё, что угодно: ставить брейкпоинты, изучать её работу по шагам или просто запустить программу — отладчик автоматически остановится при возникновении неотловленного исключения.
Но обычно такой вариант чересчур изнурителен: пока доберёшься до нужного места всеми этими «next», «step», да и ставить точку останова («break») руками каждый раз утомительно. Гораздо удобнее в нужном месте программы вставить следующую строку:
import ipdb; ipdb.set_trace()
И тогда при исполнении этой строки выполнение программы приостановится и будет запущен отладчик, — дальше можно начинать углубляться в изучение программы. По сути, функция «set_trace» — это установка точки останова (breakpoint).
Примечание для эстетов
Любой, кому знакома аббревиатура PEP8, в этом месте может обвинить меня в использовании двух команд в одной строке через точку с запятой, однако такой подход вполне имеет право на жизнь. Во-первых, это временный код, который никогда не будет закоммичен в репозиторий (об этом позаботится специальный хук, который проверяет код на соответствие PEP8 перед коммитом и пушем, а так же автопроверка кода в текстовом редакторе). Во-вторых, так проще вставлять и удалять эту строку в код. Ну и в-третьих, как написано в PEP8: «A Foolish Consistency is the Hobgoblin of Little Minds».
Python Debugger имеет ещё один режим работы, который в некоторых случаях оказывается удобнее фукнции set_trace. Он называется «post mortem»: запуск отладчика с заданным трейсбеком:
try:
some_code()
except:
import sys
import ipdb
tb = sys.exc_info()[2]
ipdb.post_mortem(tb)
или так:
import sys
import ipdb
def run_debugger(type, value, tb):
ipdb.pm()
sys.excepthook = run_debugger
some_code()
В случае возникновения любых неотлавливаемых исключений в функции «some_code» будет вызван отладчик в том месте программы, где произошло исключение.
debug
Интересной заменой функции «set_trace» является модуль debug, который просто импортирует библиотеки ipdb и see (удобная альтернатива функции dir) и запускает отладку. Единственным плюсом модуля является удобство использования, достаточно в любом месте программы вставить строку:
import debug
И при выполнении этой строки будет вызван отладчик «ipdb» и импортирован модуль «see».
ipdbplugin
Ещё одна интересная и, на этот раз, полезная библиотека: nose-ipdb. С её помощью можно автоматически запускать отладчик при ошибках (исключениях) или же просто при неверно отработанных тестах (я надеюсь, вы используете nose? =). Для запуска отладчика ipdb при ошибках, достаточно при запуске тестов добавить ключ "--ipdb":
➜ nosetests --ipdb
А для запуска отладчика при некорректно завершившихся тестов нужно добавить ключ "--ipdb-failures":
➜ nosetests --ipdb-failures
Конечно, можно ловить всё и сразу:
➜ nosetests --ipdb --ipdb-failures
Я каждый день использую этот модуль и просто не представляю себе жизни без него.
werkzeug
Потрясающий проект Армина Ронахера (Armin Ronacher), автора фреймворка Flask и вообще одного из крутейших программистов Python называется werkzeug и представляет собой сборник различных утилит для WSGI приложений. Одна из них — клёвый отладчик, который позволяет выводить удобный трейсбек ошибки, а так же запускать консоль Python в соответствующем месте трейсбека прямо на странице браузера:
Использовать его очень просто, достаточно обернуть приложение с помощью соответствующего middleware:
from werkzeug.debug import DebuggedApplication
from myapp import app
app = DebuggedApplication(app, evalex=True)
Говорят, что werkzeug умеет отлаживать даже Ajax-запросы, но, к сожалению, я сам лично никогда этого не делал. Буду рад любым комментариям на эту тему.
django-pdb
Ещё один хороший модуль, на этот раз для Django: django-pdb. Он позволяет запускать отладчик при наличии соответствующего GET-параметра в запросе (например: http://127.0.0.1:8000/app/view?ipdb) либо для всех запросов:
➜ python manage.py runserver --ipdb
Либо вызывать отладчик при возникновении исключений (режим «post-mortem»):
➜ python manage.py runserver --pm
или
POST_MORTEM = True
в settings.py.
django-extensions
Но гораздо лучше в Django использовать модуль django-extensions, который добавляет очень полезную команду runserver_plus. С помощью этой батарейки можно подружить Django и Werkzeug (см. выше) и начать получать удовольствие от страниц с пятисотой ошибкой =)
Для использования всего этого чуда достаточно запустить девелоперский сервер с помощью команды runserver_plus:
➜ python manage.py runserver_plus
sentry
Отладка программы в девелоперском окружении это, конечно, удобно и хорошо, но самое сложное — локализовать проблему по багрепорту от живого пользователя. Иногда это бывает сложно. Несмотря на все преимущества Python, модуля «телепатии» не существует, и разработчик остаётся один на один со словами пользователя «ничего не работает!!!11».
Проект Sentry позволяет сохранять каждую ошибку пользователя с текстом исключения, полным стектрейсом исключения и значениями всех локальных переменных в каждой из функций стектрейса, а так же много другой информации: окружение пользователя (браузер, ОС), все заголовки запроса и вообще всё, что пожелает разработчик.
Одинаковые ошибки группируются, таким образом можно наблюдать за «пульсом» проекта и чинить в первую очередь самые критичные места. Ещё один пример использования sentry — логгирование. Можно просто добавить в спорное место запись в лог сообщения с любыми интересующими разработчика переменными, и всё это окажется в sentry.
Но самый большой плюс в том, что всё это можно (и нужно!) использовать в продакшене.
PuDB
Ещё один интересный отладчик: PuDB представляет собой консольный дебагер с графическим интерфейсом:
Не буду много о нём писать (честно говоря, я сам активно им не пользовался), предлагаю прочитать короткую заметка на хабре: Удобный отладчик для Python/Django проектов от пользователя int22h или более полный обзор: Отладка Python/Django при помощи PuDB.
Winpdb
Standalone отладчик Python, на этот раз с полноценным графическим интерфейсом: Winpdb:
Его разработчики утверждают, что winpdb в 20 раз быстрее pdb, а так же поддерживает работу с тредами. Очень большой и подробный туториал можно найти на этой странице: code.google.com/p/winpdb/wiki/DebuggingTutorial.
IDE
Отдельного слова заслуживают универсальные «комбайны» программирования: IDE, которые позволяют не выходя из редактора запустить код, отпрофилировать его или запустить встроенный отладчик. Я, пожалуй, выделю несколько продуктов, предназначенных для разработки на Python: PyCharm, PyDev, Wing IDE и PTVS.
Лирическое отступление
К сожалению, лично я сколько не пытался, так и не смог пересилить себя и променять скорость, удивительную гибкость и удобство работы с консолью и любимым текстовым редактором (будь то vim, emacs или sublime text) на любую из вышеперечисленных IDE, однако мои коллеги успешно ими пользуются и поэтому я советую хотя бы дать этим замечательным продуктам шанс и попробовать их.
Если честно, я не вижу особого смысла рассматривать каждую из этих IDE, достаточно знать что они есть, что они успешно справляются со своими задачами и вы можете использовать встроенный отладчик прямо из редактора, — это действительно удобно, круто и здорово.
Спасибо всем, кто дочитал и прокомментировал.
Владимир Рудных,
Технический руководитель Календаря Mail.Ru.