Отладчик Python с линейной версией в программе, использующей стандартный ввод - PullRequest
0 голосов
/ 31 декабря 2018

Чтобы добавить специальную точку останова отладчика в скрипте Python, я могу вставить строку

import pdb; pdb.set_trace()

Pdb читает со стандартного ввода, так что это не работает, если сам скрипт также читает со стандартного ввода,Как обходной путь, в Unix-подобной системе я могу сказать pdb для чтения из терминала :

import pdb; pdb.Pdb(stdin=open('/dev/tty', 'r'), stdout=open('/dev/tty', 'w')).set_trace()

Это работает, но в отличие от простого pdb.set_trace, я неполучить преимущество редакции командной строки, предоставляемой библиотекой readline (клавиши со стрелками и т. д.).

Как я могу ввести pdb, не вмешиваясь в stdin и stdout скрипта, и все же получить редакцию командной строки?

В идеале один и тот же код должен работать как в Python 2, так и в Python 3. Совместимость с не-Unix системами была бы плюсом.

Игрушечная программа в качестве тестового примера:

#!/usr/bin/env python
import sys
for line in sys.stdin:
    #import pdb; pdb.set_trace()
    import pdb; pdb.Pdb(stdin=open('/dev/tty', 'r'), stdout=open('/dev/tty', 'w')).set_trace()
    sys.stdout.write(line)

Использование: { echo one; echo two; } | python cat.py

1 Ответ

0 голосов
/ 01 января 2019

Я надеюсь, что не пропустил ничего важного, но кажется, что вы не можете сделать это совершенно тривиальным способом, потому что readline будет использоваться, только если pdb.Pdb (соответственно cmd.Cmd это sublcasses) имеет use_rawinput установлен на ненулевое значение, что, однако, приведет к игнорированию stdin и смешиванию входных данных для отладчика и самого скрипта.Тем не менее, лучшее, что я придумала до сих пор:

#!/usr/bin/env python3
import os
import sys
import pdb

pdb_inst = pdb.Pdb()

stdin_called = os.fdopen(os.dup(0))
console_new = open('/dev/tty')
os.dup2(console_new.fileno(), 0)
console_new.close()
sys.stdin = os.fdopen(0)

for line in stdin_called:
    pdb_inst.set_trace()
    sys.stdout.write(line)

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

Я перенаправил (продублировал) входящий STDIN в дескриптор файла и открыл его как stdin_called.Затем (на основе вашего примера) я открыл /dev/tty для чтения, заменил дескриптор файла процесса 0 (для STDIN; лучше использовать значение, возвращаемое sys.stdin.fileno()) на этот, который я только что открыла также переназначил соответствующий файловый объект на sys.stdin.Таким образом, программы цикл и pdb используют свои собственные входные потоки, в то время как pdb взаимодействует с тем, что кажется просто «нормальной» консолью STDIN, она рада включить readline on.

Это не красиво, но следует делать то, что вы хотели, и, надеюсь, даст полезные советы.Используется (если доступно) readline (редактирование строки, история, завершение), когда в pdb:

$ { echo one; echo two; } | python3 cat.py
> /tmp/so/cat.py(16)<module>()
-> sys.stdout.write(line)
(Pdb) c
one
> /tmp/so/cat.py(15)<module>()
-> pdb_inst.set_trace()
(Pdb) con[TAB][TAB]
condition  cont       continue   
(Pdb) cont
two

Примечание, начиная с версии 3.7, вы можете использовать breakpoint() вместо import pdb; pdb.Pdb().set_trace() для удобстваи вы также можете проверить результат вызова dup2, чтобы убедиться, что дескриптор файла был создан / заменен, как и ожидалось.


РЕДАКТИРОВАТЬ: Как упоминалось ранее и отмечено в комментарии OP, это уродливои инвазивен к сценарию.Это не делает его красивее, но мы можем использовать несколько хитростей, чтобы уменьшить влияние на окружающую среду.Один такой вариант, который я взломал вместе:

import sys

# Add this: BEGIN
import os
import pdb
import inspect

pdb_inst = pdb.Pdb()

class WrapSys:
    def __init__(self):
        self.__stdin = os.fdopen(os.dup(0))
        self.__console = open('/dev/tty')
        os.dup2(self.__console.fileno(), 0)
        self.__console.close()
        self.__console = os.fdopen(0)
        self.__sys = sys

    def __getattr__(self, name):
        if name == 'stdin':
            if any((f.filename.endswith("pdb.py") for f in inspect.stack())):
                return self.__console
            else:
                return self.__stdin
        else:
            return getattr(self.__sys, name)

sys = WrapSys()
# Add this: END

for line in sys.stdin:
    pdb_inst.set_trace()  # Inject breakpoint
    sys.stdout.write(line)

Я не копал весь путь, но, как есть, pdb / cmd действительно, кажется, не только нужен sys.stdin, но и дляиспользовать fd 0 для включения readline. Приведенный выше пример поднимает уровень и в нашем скрипте перехватывает то, что означает sys, чтобы установить другое значение для sys.stdin, когда код из pdb.py находится в стеке.Одно очевидное предостережение.Если что-то еще, кроме pdb, также ожидает и зависит от sys.stdin fd, равным 0, это все равно будет неудачей (или чтением его входных данных из другого потока, если он просто пошел на это).

...