在使用标准输入的程序中使用行版的 Python 调试器

标签 python python-3.x stdin readline pdb

要在 Python 脚本中添加临时调试器断点,我可以插入以下行

import pdb; pdb.set_trace()

Pdb 从标准输入中读取,因此如果脚本本身也从标准输入中读取,这将不起作用。作为解决方法,在类 Unix 系统上,我可以 tell pdb to read from the terminal :

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

这有效,但与普通的 pdb.set_trace 不同,我没有得到 readline 库提供的命令行编辑的好处(箭头键等)。

如何在不干扰脚本的标准输入和标准输出的情况下输入 pdb,并仍然获得命令行版本?

理想情况下,相同的代码应该可以在 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;回声二; } | python cat.py

最佳答案

我希望我没有错过任何重要的事情,但似乎你不能以一种完全微不足道的方式真正做到这一点,因为 readline 只会在 pdb.Pdb 时被使用> (resp. cmd.Cmd it 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

它并不漂亮,但应该可以满足您的需求,希望它能提供有用的提示。它在 pdb 中使用(如果可用)readline(行编辑、历史记录、完成):

$ { 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代表当 pdb.py 的代码在堆栈上时,为 sys.stdin 预设不同的含义。一个明显的警告。如果除 pdb 之外的任何其他内容也期望并依赖于 sys.stdin fd 为 0,它仍然会倒霉(或阅读它的输入来自不同的流,如果它只是为了它)。

关于在使用标准输入的程序中使用行版的 Python 调试器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53982611/

相关文章:

python - 使用Python进行带有开始和结束日期和时间的url格式化

c - 从 stdin、C 读取未知行数

go - Golang解决hackerrank的“对角线差异”问题

bash - "<<(command-here)"shell 习语导致 "redirection unexpected"

python - 在 Netbeans 中设置 Python

python - 错误 ModuleNotFoundError : No module named 'azure.keyvault.secrets' although I installed the package

python - 如何限制没有终端或多处理库的 python 脚本使用的 CPU 数量?

python - 在 Django 中注册时将用户添加到组

尽管有随机组件,但当我多次调用一个函数时,Python 给出相同的结果

python - dict.update() 是线程安全的吗?