#python #debugging
#python #отладка
Вопрос:
Я пишу небольшую среду разработки python IDE и хочу добавить простую отладку. Мне не нужны все возможности winpdb. Как мне запустить программу на python (по имени файла) с точкой останова, установленной в номере строки, чтобы она выполнялась до этого номера строки и останавливалась? Обратите внимание, что я не хочу делать это из командной строки, и я не хочу редактировать исходный код (например, вставив set_trace). И я не хочу, чтобы он останавливался на первой строке, поэтому я должен запустить отладчик оттуда. Я перепробовал все очевидные способы с pdb и bdb, но, должно быть, я что-то упускаю.
Комментарии:
1. Итак, вы не хотите использовать командную строку и не хотите редактировать исходный код… о каком другом способе вы думаете?
Ответ №1:
Практически единственный жизнеспособный способ сделать это (насколько я знаю) — запустить Python как подпроцесс из вашей IDE. Это позволяет избежать «загрязнения» текущим интерпретатором Python, что делает довольно вероятным, что программа будет выполняться так же, как если бы вы запустили ее независимо. (Если у вас возникли проблемы с этим, проверьте среду подпроцесса.) Таким образом, вы можете запустить скрипт в «режиме отладки» с помощью
p = subprocess.Popen(args=[sys.executable, '-m', 'pdb', 'scriptname.py', 'arg1'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
Это запустит Python в приглашении отладчика. Вам нужно будет выполнить некоторые команды отладчика, чтобы установить точки останова, которые вы можете сделать следующим образом:
o,e = p.communicate('break scriptname.py:lineno')
Если это работает, o
должен быть обычный вывод интерпретатора Python после установки точки останова и e
должен быть пустым. Я бы посоветовал вам поиграть с этим и добавить некоторые проверки в свой код, чтобы убедиться, что точки останова были установлены правильно.
После этого вы можете запустить программу с помощью
p.communicate('continue')
На этом этапе вы, вероятно, захотите подключить потоки ввода, вывода и ошибок к консоли, которую вы встраиваете в свою IDE. Вероятно, вам нужно будет сделать это с помощью цикла событий, примерно так:
while p.returncode is None:
o,e = p.communicate(console.read())
console.write(o)
console.write(e)
Вы должны считать, что этот фрагмент фактически является псевдокодом, поскольку в зависимости от того, как именно работает ваша консоль, вероятно, потребуется немного поработать, чтобы все было правильно.
Если это кажется чрезмерно запутанным, вы, вероятно, можете немного упростить процесс, используя функции Python pdb
и bdb
модулей (я предполагаю, что «Python debugger» и basic debugger» соответственно). Лучшим справочником о том, как это сделать, является исходный код самого pdb
модуля. По сути, разделение обязанностей модулей заключается в том, что bdb
они обрабатывают «скрытые» функции отладчика, такие как установка точек останова или остановка и перезапуск выполнения; pdb
это оболочка вокруг этого, которая обрабатывает взаимодействие с пользователем, то есть чтение команд и отображение выходных данных.
Для вашего интегрированного в IDE отладчика было бы разумно настроить поведение pdb
модуля двумя способами, которые я могу придумать:
- автоматически устанавливайте точки останова во время инициализации, без необходимости явно отправлять текстовые команды для этого
- заставьте его принимать входные данные и отправлять выходные данные на консоль вашей IDE
Просто эти два изменения должны быть легко реализованы с помощью подклассов pdb.Pdb
. Вы можете создать подкласс, инициализатор которого принимает список точек останова в качестве дополнительного аргумента:
class MyPDB(pdb.Pdb):
def __init__(self, breakpoints, completekey='tab',
stdin=None, stdout=None, skip=None):
pdb.Pdb.__init__(self, completekey, stdin, stdout, skip)
self._breakpoints = breakpoints
Логическое место для фактической настройки точек останова — это сразу после того, как отладчик прочитает свой .pdbrc
файл, что происходит в pdb.Pdb.setup
методе. Для выполнения фактической настройки используйте set_break
метод, унаследованный от bdb.Bdb
:
def setInitialBreakpoints(self):
_breakpoints = self._breakpoints
self._breakpoints = None # to avoid setting breaks twice
for bp in _breakpoints:
self.set_break(filename=bp.filename, line=bp.line,
temporary=bp.temporary, conditional=bp.conditional,
funcname=bp.funcname)
def setup(self, f, t):
pdb.Pdb.setup(self, f, t)
self.setInitialBreakpoints()
Этот фрагмент кода будет работать для каждой точки останова, передаваемой, например, как именованный кортеж. Вы также можете поэкспериментировать с непосредственным созданием bdb.Breakpoint
экземпляров напрямую, но я не уверен, будет ли это работать должным образом, поскольку bdb.Bdb
поддерживает собственную информацию о точках останова.
Далее вам нужно будет создать новый main
метод для вашего модуля, который запускает его так же, как pdb
выполняется. В какой-то степени вы можете скопировать main
метод из pdb
(и if __name__ == '__main__'
инструкцию, конечно), но вам нужно будет дополнить его каким-то способом передачи информации о ваших дополнительных точках останова. Я бы предложил записать точки останова во временный файл из вашей IDE и передать имя этого файла в качестве второго аргумента:
tmpfilename = ...
# write breakpoint info
p = subprocess.Popen(args=[sys.executable, '-m', 'mypdb', tmpfilename, ...], ...)
# delete the temporary file
Затем mypdb.main()
вы должны добавить что-то вроде этого:
def main():
# code excerpted from pdb.main()
...
del sys.argv[0]
# add this
bpfilename = sys.argv[0]
with open(bpfilename) as f:
# read breakpoint info
breakpoints = ...
del sys.argv[0]
# back to excerpt from pdb.main()
sys.path[0] = os.path.dirname(mainpyfile)
pdb = Pdb(breakpoints) # modified
Теперь вы можете использовать свой новый модуль отладчика точно так же, как вы бы использовали pdb
, за исключением того, что вам не нужно явно отправлять break
команды перед запуском процесса. Преимущество этого заключается в том, что вы можете напрямую подключать стандартный ввод и вывод подпроцесса Python к вашей консоли, если это позволяет вам это сделать.
Комментарии:
1. Спасибо за информацию. Но я получаю ValueError: операция ввода-вывода для закрытого файла, как только он попадает в p.communicate(‘continue’). Использование close_fds=False во всплывающем окне не помогает…
2. @antwin: помните, что все это, по сути, псевдокод. Если вы просто запустите его как есть, конечно, вы получите всевозможные ошибки. Вам нужно адаптировать его к своему собственному проекту способами, о которых я ничего не мог бы вам рассказать, не имея доступа к вашему исходному коду.
3. Действительно! Итак, я извлек ваше предложение в следующий файл #!/usr/bin/python import sys,subprocess fileName = ‘/tmp/test.py ‘lineno = 4 p = подпроцесс. Popen(args=[sys.executable, ‘-m’, ‘pdb’, fileName], stdin=подпроцесс. КАНАЛ, стандартный вывод = подпроцесс. КАНАЛ, stderr=подпроцесс. СТАНДАРТНЫЙ ВЫВОД) cmd = ‘break %s:%d’ % (fileName,lineno) ;печать cmd o,e = p.связь (cmd) p.связь (‘продолжить’) Это должно работать, и я использовал подобное раньше, но он по-прежнему выдает ошибку ValueError: операция ввода-вывода для закрытого файла после первого сообщения…