pythonpython-3.xdebuggingpdb

Python breakpoint() automatically reads all STDIN -- how to disable it?


Here is a sample python script.

import sys
print("Hello, world!")
for i, line in enumerate(sys.stdin):
    print(line)
    print(f"Before breakpoint: {i}")
    breakpoint()
    print(f"After breakpoint: {i}")

Running seq 1 10 | python tmp.py launches debugger at the specified breakpoint, however, it automatically reads all the stdin.

seq 1 10 | python tmp.py 
Hello, world!
1

Before breakpoint: 0
> .../tmp.py(9)<module>()
-> print(f"After breakpoint: {i}")
(Pdb) 2
(Pdb) 3
(Pdb) 4
(Pdb) 5
(Pdb) 6
(Pdb) 7
(Pdb) 8
(Pdb) 9
(Pdb) 10
(Pdb) 
Traceback (most recent call last):
  File ".../tmp.py", line 9, in <module>
    print(f"After breakpoint: {i}")
  File ".../tmp.py", line 9, in <module>
    print(f"After breakpoint: {i}")
  File ".../python3.10/bdb.py", line 90, in trace_dispatch
    return self.dispatch_line(frame)
  File ".../python3.10/bdb.py", line 115, in dispatch_line
    if self.quitting: raise BdbQuit
bdb.BdbQuit

How to stop breakpoint() from reading STDIN? i.e., I still want breakpoint() but just don't want it to automatically consume and execute STDIN. I looked into the docs[1] and it doesn't mention about this STDIN behavior, nor an option to disable it.


[1] https://docs.python.org/3.10/library/functions.html?highlight=breakpoint#breakpoint. I am using Python 3.10.9 on Ubuntu 20.04.6 LTS (WSL)


Solution

  • Answering my own question. Thanks to the comments, and credits to this answer [1], here is a solution that works.

    The trick is to attach TTY as STDIN/OUT/ERR for the PDB while leaving STDIN/OUT/ERR intact.

    import sys
    
    print("Hello, world!")
    
    def tty_pdb():
        from contextlib import (_RedirectStream,
                                redirect_stdout, redirect_stderr)
        class redirect_stdin(_RedirectStream):
            _stream = 'stdin'
        with open('/dev/tty', 'r') as new_stdin, \
             open('/dev/tty', 'w') as new_stdout, \
             open('/dev/tty', 'w') as new_stderr, \
             redirect_stdin(new_stdin), \
             redirect_stdout(new_stdout), redirect_stderr(new_stderr):
            __import__('pdb').set_trace()
    
    for i, line in enumerate(sys.stdin):
        print(line)
        print(f"Before breakpoint: {i}")
        tty_pdb()
        print(f"After breakpoint: {i}")
    

    It's elegant -- no need to tweak the scripts that invoke python program! Credits to the author of [1] (I've already upvoted their answer).

    [1] - https://stackoverflow.com/a/48430325/1506477