使用stdin的程序中带有行版的Python调试器

时间:2018-12-31 00:39:54

标签: 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库提供的命令行版本(箭头键等)中受益。

如何在不干扰脚本的stdin和stdout的情况下输入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; echo two; } | python cat.py

1 个答案:

答案 0 :(得分:2)

我希望我没有错过任何重要的事情,但是看来您真的不能以一种完全琐碎的方式做到这一点,因为readline仅在pdb.Pdb({{1} } it sublcasses)已将cmd.Cmd设置为非零,但这将导致忽略您的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进行交互,它很高兴启用{{1 }}。

这不是很漂亮,但是应该照做您想做的事,希望它会提供有用的提示。在STDIN中时,它使用readline(如果有){行编辑,历史记录,完成情况):

readline

请注意,从3.7版开始,为方便起见,您可以使用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 ,还可以检查breakpoint()调用的结果以确保文件描述符已按预期创建/替换


编辑:如前所述,OP在评论中指出,此脚本既丑陋又具有侵入性。它并没有使其更漂亮,但是我们可以采取一些技巧来减少对其周围环境的影响。我一起砍过一个这样的选项:

import pdb; pdb.Pdb().set_trace()

我还没有完全研究过,但实际上dup2 / 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,而且还需要使用fd cmd为了使sys.stdin生效。上面的示例占用了一个缺口,并且在脚本中劫持了0的含义,以便在{{1 }}在堆栈上。一个明显的警告。如果readline之外的其他任何事物也期望并且取决于sys fd为sys.stdin,那么它仍然会很不幸(或者如果只是为了它)。