10 December 2013

Профилирование и отладка Python, отладка

Mail.ru Group corporate blogPythonDjango
В предыдущей статье мы закончили разговор о профилировании обзором событийных профайлеров.

Сегодня я предлагаю рассмотреть методы отладки программ.




Прежде, чем начинать разговор об отладке, нам нужно определиться, что же это такое. По традиции, обратимся к Википедии: «Отладка — обнаружение, локализация и устранение ошибок». Вообще, для успешной отладки программы, нам необходимо (но не всегда достаточно) две вещи: значение переменных в том месте программы, в котором произошла ошибка, а так же стектрейс: порядок вызова функций, вплоть до ошибочной. Полезно так же знать об окружении, в котором выполняется программа: наличие (или отсутствие) свободной памяти, системные ограничения (например, на количество файловых дескрипторов) и так далее, но это слегка выходит за пределы нашей статьи.

Классика жанра


Что делает начинающий программист, когда хочет «проникнуть» внутрь программы и изучить содержимое переменных (в нужных местах) и логику работы программы (вызовы функций, выполнение условий)? Он прибегает к самому распространённому, самому простому и самому «действенному» способу отладки: расставляет по всему коду «принты» (оператор 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.
Tags:profilingpythondjango
Hubs: Mail.ru Group corporate blog Python Django
+46
78.4k 328
Comments 26