Как подключить отладчик к подпроцессу Python?
Мне нужно отладить дочерний процесс, порожденный multiprocessing.Process()
, pdb
Похоже, что degugger не знает о разветвлении и не может подключиться к уже запущенным процессам.
Существуют ли более умные отладчики Python, которые можно подключить к подпроцессу?
10 ответов
Winpdb - это определение более умного отладчика Python. Он явно поддерживает переход на развилку, но не уверен, что multiprocessing.Process()
но стоит попробовать.
Список кандидатов для проверки поддержки вашего варианта использования см. В списке отладчиков Python в вики.
Я искал простое решение этой проблемы и придумал это:
import sys
import pdb
class ForkedPdb(pdb.Pdb):
"""A Pdb subclass that may be used
from a forked multiprocessing child
"""
def interaction(self, *args, **kwargs):
_stdin = sys.stdin
try:
sys.stdin = open('/dev/stdin')
pdb.Pdb.interaction(self, *args, **kwargs)
finally:
sys.stdin = _stdin
Используйте его так же, как и классический Pdb:
ForkedPdb().set_trace()
Это разработка ответа Ромуальда, который восстанавливает исходный стандартный ввод с помощью файлового дескриптора. Это позволяет readline работать внутри отладчика. Кроме того, в pdb специальное управление KeyboardInterrupt отключено, чтобы не мешать многопроцессорному обработчику sigint.
class ForkablePdb(pdb.Pdb):
_original_stdin_fd = sys.stdin.fileno()
_original_stdin = None
def __init__(self):
pdb.Pdb.__init__(self, nosigint=True)
def _cmdloop(self):
current_stdin = sys.stdin
try:
if not self._original_stdin:
self._original_stdin = os.fdopen(self._original_stdin_fd)
sys.stdin = self._original_stdin
self.cmdloop()
finally:
sys.stdin = current_stdin
remote-pdb можно использовать для отладки подпроцессов. После установки добавьте в код, который нужно отладить, следующие строки:
import remote_pdb
remote_pdb.set_trace()
remote-pdb напечатает номер порта, который будет принимать соединение Telnet для отладки этого конкретного процесса. Есть некоторые предостережения в отношении порядка запуска воркеров, где stdout используется при использовании различных интерфейсов и т. Д. Чтобы гарантировать использование определенного порта (должен быть свободным и доступным для текущего пользователя), используйте вместо этого следующее:
from remote_pdb import RemotePdb
RemotePdb('127.0.0.1', 4444).set_trace()
remote-pdb также можно запустить черезbreakpoint()
команда в Python 3.7.
Опираясь на идею @memplex, мне пришлось изменить ее, чтобы она работала с joblib
установив sys.stdin
в конструкторе, а также передавая его напрямую через joblib.
<!-- language: lang-py -->
import os
import pdb
import signal
import sys
import joblib
_original_stdin_fd = None
class ForkablePdb(pdb.Pdb):
_original_stdin = None
_original_pid = os.getpid()
def __init__(self):
pdb.Pdb.__init__(self)
if self._original_pid != os.getpid():
if _original_stdin_fd is None:
raise Exception("Must set ForkablePdb._original_stdin_fd to stdin fileno")
self.current_stdin = sys.stdin
if not self._original_stdin:
self._original_stdin = os.fdopen(_original_stdin_fd)
sys.stdin = self._original_stdin
def _cmdloop(self):
try:
self.cmdloop()
finally:
sys.stdin = self.current_stdin
def handle_pdb(sig, frame):
ForkablePdb().set_trace(frame)
def test(i, fileno):
global _original_stdin_fd
_original_stdin_fd = fileno
while True:
pass
if __name__ == '__main__':
print "PID: %d" % os.getpid()
signal.signal(signal.SIGUSR2, handle_pdb)
ForkablePdb().set_trace()
fileno = sys.stdin.fileno()
joblib.Parallel(n_jobs=2)(joblib.delayed(test)(i, fileno) for i in range(10))
Просто используйте PuDB , который дает вам потрясающий TUI (GUI на терминале) и поддерживает многопроцессорность следующим образом:
from pudb import forked; forked.set_trace()
Проблема здесь в том, что Python всегда подключается в дочернем процессе кos.devnull
чтобы избежать конкуренции за поток. Но это означает, что когда отладчик (или простойinput()
) пытается подключиться к стандартному вводу, чтобы получить ввод от пользователя, он сразу достигает конца файла и сообщает об ошибке.
Одним из решений, по крайней мере, если вы не ожидаете одновременного запуска нескольких отладчиков, является повторное открытие stdin в дочернем процессе . Это можно сделать, установивsys.stdin
кopen(0)
, который всегда открывает активный терминал. Это на самом деле то, чтоForkedPdb
решение делает, но это можно сделать более простым и независимым от ОС способом, например:
import multiprocessing, sys
def main():
process = multiprocessing.Process(target=worker)
process.start()
process.join()
def worker():
# Python automatically closes sys.stdin for the subprocess, so we reopen
# stdin. This enables pdb to connect to the terminal and accept commands.
# See https://stackoverflow.com/a/30149635/3830997.
sys.stdin = open(0) # or os.fdopen(0)
print("Hello from the subprocess.")
breakpoint() # or import pdb; pdb.set_trace()
print("Exited from breakpoint in the subprocess.")
if __name__ == '__main__':
main()
У меня была идея создать "фиктивные" классы, чтобы имитировать реализацию методов, которые вы используете из многопроцессорной обработки:
from multiprocessing import Pool
class DummyPool():
@staticmethod
def apply_async(func, args, kwds):
return DummyApplyResult(func(*args, **kwds))
def close(self): pass
def join(self): pass
class DummyApplyResult():
def __init__(self, result):
self.result = result
def get(self):
return self.result
def foo(a, b, switch):
# set trace when DummyPool is used
# import ipdb; ipdb.set_trace()
if switch:
return b - a
else:
return a - b
if __name__ == '__main__':
xml = etree.parse('C:/Users/anmendoza/Downloads/jim - 8.1/running-config.xml')
pool = DummyPool() # switch between Pool() and DummyPool() here
results = []
results.append(pool.apply_async(foo, args=(1, 100), kwds={'switch': True}))
pool.close()
pool.join()
results[0].get()
Вот версия ForkedPdb(решение Ромуальда), которая будет работать для систем на базе Windows и *nix.
import sys
import pdb
import win32console
class MyHandle():
def __init__(self):
self.screenBuffer = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
def readline(self):
return self.screenBuffer.ReadConsole(1000)
class ForkedPdb(pdb.Pdb):
def interaction(self, *args, **kwargs):
_stdin = sys.stdin
try:
if sys.platform == "win32":
sys.stdin = MyHandle()
else:
sys.stdin = open('/dev/stdin')
pdb.Pdb.interaction(self, *args, **kwargs)
finally:
sys.stdin = _stdin
Если вы на поддерживаемой платформе, попробуйте DTrace. Большинство семейства BSD / Solaris / OS X поддерживают DTrace.
Вот вступление автора. Вы можете использовать Dtrace для отладки чего угодно.
Вот ТАК сообщение об обучении DTrace.