Рубрики
Без рубрики

Как взломать графический отладчик Python

Автор оригинала: Cristian Medina.

Ноль-отладки в 15 минутах

Вы не осознаете ценность отладчика, пока вы не будете не работать с трудно визуализации проблемы. Но как только вы выстрелите среду развития с достойными возможностями отладки, вы никогда не оглядываетесь назад.

Хотите знать, где вы находитесь в выполнении кода? Что такое так долго? Просто пауза и проверь.

Интересно, какое значение назначено этой переменной? Наведите курсор на нее.

Хотите пропустить кучу кода и продолжать работать из другого раздела? Действуй.

Иногда Печать (variable_name) Недостаточно, чтобы дать вам представление о том, что происходит с вашим проектом. Это когда хороший отладчик может помочь вам выяснить вещи.

Python уже дает вам встроенный отладчик в форме PDB (инструмент командной строки). Но благодаря удивительным сообществу Python есть более варианты, которые имеют графические интерфейсы. И есть тонна интегрированных средов разработчика (IDES), которые работают с Python, такие как Jetbrain’s Pycharm , Wingare’s Wingide и даже Сообщество Microsoft Visual Studio Отказ

Но вы здесь не слышать, как один отладчик лучше другого, или какой из них красивее или более элегантно. Вы здесь, чтобы узнать, насколько просто написать отладчик Python, который шаги по вашему коду. Это дает вам проблеск в внутренние органы Питона.

Я собираюсь показать вам, как вы можете построить один, и при этом поцарапать зуд, у меня было долгое время.

Теперь давайте доберемся до этого.

Быстрый праймер на том, как Code Python организован и обработан

Вопреки распространенному убеждению, Python на самом деле скомпилированный язык. Когда вы выполняете код, ваш модуль проходит через компилятор, который выплевывает Bytecode который кэшируется как .pyc или __pycache__ файлы. Сам Битекод – это то, что позже выполнена строка по линии.

Фактически, фактический код CPYPHON, который запускает программу, – это не что иное, как оператор регистратора гигантского переключателя работает в цикле. Это оператор If-else, который смотрит на байт-код инструкции, затем расположены его на основе того, на что предназначена эта операция.

Исполняемые инструкции по Bytecode внутренне упоминаются как Кодовые объекты и дес и проверять Модули используются для их производства или интерпретации. Это неизменные структуры, которые, хотя и на ссылках других объектов – как функции – не содержат никаких ссылок сами.

Вы можете легко посмотреть на Bytecode, который представляет собой любой данный источник через dis.dis () Отказ Просто попробуйте с случайной функцией или классом. Это аккуратное упражнение, которое поможет вам визуализировать то, что происходит. Вывод будет выглядеть что-то подобное:

>>> def sample(a, b):
...     x = a + b
...     y = x * 2
...     print('Sample: ' + str(y))
...
>>> import dis
>>> dis.dis(sample)
2       0 LOAD_FAST                0 (a)
        3 LOAD_FAST                1 (b)
        6 BINARY_ADD
        7 STORE_FAST               2 (x)
3      10 LOAD_FAST                2 (x)
       13 LOAD_CONST               1 (2)
       16 BINARY_MULTIPLY
       17 STORE_FAST               3 (y)
4      20 LOAD_GLOBAL              0 (print)
       23 LOAD_CONST               2 ('Sample: ')
       26 LOAD_GLOBAL              1 (str)
       29 LOAD_FAST                3 (y)
       32 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
       35 BINARY_ADD
       36 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
       39 POP_TOP
       40 LOAD_CONST               0 (None)
       43 RETURN_VALUE

Обратите внимание, что каждая строка в Bytecode ссылается на его соответствующую позицию в исходном коде в левой колонке, и что это не отношение к одному к одному. Может быть несколько меньших – можно даже сказать, что атомные операции, которые состоит из настроек более высокого уровня.

А Рамка объекта В Python представляет собой кадр выполнения. Он содержит ссылку на объект кода, который в данный момент выполняет, локальные переменные, с которыми он работает, глобальные имена (переменные), которые доступны, и ссылки на любые связанные кадры (например, родитель, которые его породили).

Здесь гораздо подробнее об этих объектах, чтобы обсудить здесь, но, надеюсь, этого достаточно, чтобы намочить свой аппетит. Вам не понадобится гораздо больше для целей нашего отладчика, хотя вы должны проверить глубже раздел погружения для ссылок на то, где смотреть дальше.

Введите модуль SYS

Python предоставляет ряд утилит в своей стандартной библиотеке через sys модуль. Не только есть такие вещи, как sys.path чтобы получить путь Python или sys.platform Чтобы помочь найти подробную информацию о ОС, в которой вы бежите, но есть и sys.settrace () и sys.setprofile () Чтобы помочь написать языковые инструменты.

Да, вы прочитали это правильно. Python уже имеет встроенные крючки, чтобы помочь анализировать код и взаимодействовать с выполнением программы. sys.settrace () Функция позволит вам запустить обратный вызов при выполнении выполнения нового объекта кадра и дает нам ссылку на него, что, в свою очередь, предоставляет объект кода, с которым вы работаете.

Для быстрого примера того, как это выглядит, давайте повторно использовать функцию более раннего:

def sample(a, b):
    x = a + b
    y = x * 2
    print('Sample: ' + str(y))

Предполагая, что каждый раз, когда выполняется новый кадр, вы хотите обратный вызов, который печатает объект кода и номер строки, его выполнение, вы можете определить его как:

def trace_calls(frame, event, arg):
    if frame.f_code.co_name == "sample":
        print(frame.f_code)

Теперь это просто вопрос установки его как нашего обратного вызова:

sys.settrace(trace_calls)

И выполнение Образец (3,2) следует производить

$ python debugger.py

Sample: 10

Вам нужна IF-оператор для фильтрации вызовов функций. В противном случае вы увидите целую кучу вещей, о которых вы не заботитесь, особенно при печати на экран. Попытайся.

Объекты кода и рамки имеют несколько полей для описания того, что они представляют. К ним относятся такие вещи, как файл выполняется, функция, имена переменных, аргументы, номера строк и список. Они являются фундаментальными для выполнения любого кода Python, и вы можете пройти через языковую документацию для более подробной информации.

Что если вы хотите отладить каждую строку?

Механизм трассировки будет установлен последующие обратные вызовы в зависимости от возвращаемого значения первого обратного вызова. Возвращение Нет Значит, вы закончите, при этом возвращающую другую функцию эффективно устанавливает ее в качестве функции трассировки внутри этого кадра.

Вот что это выглядит как:

5    def sample(a, b):
6        x = a + b
7        y = x * 2
8        print('Sample: ' + str(y))
9
10   def trace_calls(frame, event, arg):
11       if frame.f_code.co_name == "sample":
12           print(frame.f_code)
13           return trace_lines
14       return
15
16   def trace_lines(frame, event, arg):
17       print(frame.f_lineno)

Теперь, если вы выполните тот же код, что и раньше, вы можете увидеть, что он распечатает номера строк, когда вы прогрессируете через него:

$ python .\test.py

6
7
8
Sample: 10
8

Положить пользовательский интерфейс перед ним

Используя Sofi Модуль Python, вы можете легко создать веб-приложение, которое напрямую взаимодействует с нашим кодом Python.

Вот что вы бы делаете:

  1. Показать файл, имя функции и номер строки.
  2. Покажите код для текущего кадра с указателем, идентифицирующей строку.
  3. Показать значение локальных переменных.
  4. Обеспечить пошаговое выполнение, то есть, что вы должны заблокировать перед выполнением строки, пока пользователь не нажимает кнопку.
  5. Добавить функциональность по шагов.
  6. Добавьте шагиный механизм.
  7. Обеспечить способ остановки выполнения.

С точки зрения пользовательского интерфейса, # 1, # 2 и # 3 все могут быть обработаны через Bootstrap Панель где № 1 является заголовка, а # 2 и # 3 являются частью тела, завернутого в Самп Теги, чтобы показать правильное расстояние.

Поскольку интерфейс, по существу, блок ожидает ввода пользователя, и отладчик ждет команды STOP/GO, это хорошая идея для отделения этих циклов событий, используя наш старый друг Многопроцессор Отказ Затем вы можете реализовать один очередь Чтобы отправить команды отладки на один процесс, и другой очередь приложения для обновлений пользовательских интерфейсов в другой.

Благодаря многопроцессорным очередям, легко заблокировать отладчик, ожидающий пользовательских команд на trace_lines Функция с использованием .get () метод.

Если команда дана перейти к следующей строке кода (# 4), все остается прежним, в то же время выходит (# 6) изменит возвращаемое значение обратно в trace_calls Функция – эффективно удаление дальнейших звонков в Trace_Lines – и остановка (№ 7) поднимет пользовательское исключение, которое будет прервать исполнение.

# Block until you receive a debug command
cmd = trace_lines.debugq.get()
if cmd == 'step':
    # continue stepping through lines, return this callback
    return trace_lines
elif cmd == 'stop':
    # Stop execution
    raise StopExecution()
elif cmd == 'over':
    # step out or over code, so point to trace_calls
    return trace_calls
class StopExecution(Exception):
    """Custom exception used to abort code execution"""
    pass

Растение функциональность (# 5) реализована на trace_calls Уровень, никогда не возвращая обратный вызов Trace_Lines.

cmd = trace_lines.debugq.get()
if cmd == 'step':
    return trace_lines
elif cmd == 'over':
    return

Да, я прикрепил объекты очереди в качестве свойств функций следования для упрощения прохождения вещей вокруг. Функции, являющиеся объектами – отличная идея, хотя вы тоже не должны злоупотреблять.

Теперь это просто вопрос настройки виджетов для отображения данных и кнопок для управления потоком.

Вы можете вытащить исходный код из объекта кода выполнения кадра с использованием модуля проверки.

source = inspect.getsourcelines(frame.f_code)[0]

Теперь это вопрос форматирования его линии на линии Div и Самп Теги, добавление индикатора другого цвета на текущую строку (доступно через F_Lineeno и co_firstline ) и придерживаясь, что в панель Тело виджета, наряду со строковым представлением местных жителей кадра (какой простой словарь в любом случае):

def formatsource(source, firstline, currentline):
    for index, item  in enumerate(source):
        # Create a div for each line to better control format
        div = Div()
        # Extremly simplified tab index check to add blank space
        if item[0:1] == '\t' or item[0:1] == ' ':
            div.style ='margin-left:15px;'
        # If this currently executing this line, add a red mark
        if index == lineno - firstlineno:
            div.addelement(Bold('> ', style="color:red"))
        # Add the formatted code to the div
        div.addelement(Sample(item.replace("\n", "")))
        # Output the html that represents that div
        source[index] = str(div)
    return "".join(source)

Только осталось сделать, это зарегистрировать несколько обратных вызовов событий для кнопки кнопки, которые управляют потоком выполнения, добавив их соответствующие команды в очередь отладки. Вы делаете это внутри нагрузка Обработчик событий, который триггеры после исходного контента заканчивают загрузку

@asyncio.coroutine
def load(event):
    """Called when the initial html finishes loading"""
    # Start the debug process
    debugprocess.start()
    # Register click functions
    app.register('click', step, selector="#code-next-button")
    app.register('click', stop, selector="#code-stop-button")
    app.register('click', over, selector="#code-over-button")
    # Make sure the display updates
    yield from display()
@asyncio.coroutine
def step(event):
    debugq.put("step")
    # Make sure the display updates
    yield from display()
@asyncio.coroutine
def stop(event):
    debugq.put("stop")
@asyncio.coroutine
def over(event):
    debugq.put("over")

Как бы этот взгляд?

Для того чтобы весь код, собранный вместе, посмотрите проект Sofi-Debugger на GitHub:

TryexceptPass/Sofi-Debugger Способствуйте разработку Sofi-Debugger, создав счет на GitHub. github.com.

Некоторые заметки о том, что вы только что сделал

Функции из sys Упомянутый здесь модуль реализован в CPYthon и может быть недоступным в других ароматах или переводчиках. Обязательно удерживайте это в виду, когда экспериментируют.

Они также специально предназначены для использования с отладчиками, профилировщиками или аналогичными инструментами трассировки. Это означает, что вы не должны взаимодействовать с ними как часть обычной программы, или вы можете столкнуться с некоторыми непреднамеренными последствиями, особенно при взаимодействии с другими модулями, которые могут специально ориентироваться на эти одинаковые интерфейсы (например, фактические отладчики).

Дайвинг глубже

Для более глубокого погружения в языковые конструкции Python, кадры, объекты кода и модуль DIS, я решительно рекомендую, чтобы вы отложили некоторое время и проходите через внутренние лекции CPYPHON Phillip Guo (@Pbovine).

Если вам понравилась статья и захочется Подробнее о Практике Python и Software, пожалуйста, посетите tryexceptPass.org Отказ Оставайтесь в курсе своего последнего контента, подписываясь на Список рассылки Отказ