Breaking out of a while loop with a key press?

Bengt Richter bokr at oz.net
Tue Apr 1 00:00:01 EST 2003


On Mon, 31 Mar 2003 16:12:20 +0100, "Richard" <richardd at hmgcc.gov.uk> wrote:

>Hi,
>
>Can anyone suggest a simple way of detecting a key press and existing the
>program? I have a program which at the moment uses a while True: statement.
>However this is not ideal as I have to close down the console window to
>exist the program. Can anyone tell me the proper way of doing this? So that
>a CTRL-C, for example, can quit the program correctly?
>
Some time ago I coded this little experiment in interrupting a loop,
including keyboard input echoing the catching of Ctrl-C and Ctrl-Break
and putting off loop exit until the while condition. Perhaps you can
transform it into something serving your purposes. IIRC, if you comment
out the kbhit line, you will get different behavior. I don't recall testing
it on linux. Too lazy right now ;-) Note that the loop ends on the enter key
only if there was a Ctrl-C or Ctrl-Break during the line input and you have
not backspaced over their echoed representation (which should take one backspace
for one control char, even though the echo is multichar).

====< interruptpy.py >================================================
#!/usr/bin/python
# maybe useful part
import signal
class SigHandler:
    def __init__(self):
        self.signaled = 0
        self.sn=None
    
    reset = __init__
    
    def __call__(self, sn, sf):
        self.sn = sn    # latest
        self.signaled += 1
        
def test():
    import msvcrt

    sh = SigHandler()
    old_SIGINT_Handler = signal.signal(signal.SIGINT,sh)
    old_SIGBREAK_Handler = signal.signal(signal.SIGBREAK,sh)
    signames = {
        signal.SIGINT:'SIGINT',
        signal.SIGBREAK:'SIGBREAK'
    }

    # just for this demo, but main loop might be useful
    def puts(s):    # helper
        for c in s: msvcrt.putch(c)
    CTRL_C_ECHO = '<CC>'    # same length as repr, to line up
    SIGINT_ECHO = '<Ctrl-C signal seen>'
    SIGBREAK_ECHO = '<Ctrl-Break signal seen>'

    # this plays the role of your loop body, see loop below           
    def dostuff():
        print 'Enter a line: ',
        line = c = ''
        sigEchoes=[]
        sh.reset()
        while c != '\r':    # Enter key ends input
            # we could break on signals, but we want to finish line, so
            # just echo and record them FTHOI
            if sh.signaled and sh.sn:
                sigEchoes.append((len(line),sh.sn))    # log where
                if sh.sn==signal.SIGBREAK:
                    puts(SIGBREAK_ECHO)
                elif sh.sn==signal.SIGINT:
                    puts(SIGINT_ECHO)
                sh.sn = 0   # reset latest flag
            if not msvcrt.kbhit(): continue # apparently screens ctrl-c?
            c = msvcrt.getch()      # should get ctrl-c but not ctrl-break?
            if c=='\r': break # Enter
            if c=='\b':
                if sigEchoes and len(line)==sigEchoes[-1][0]:
                    # "erase" break effect"
                    puts('\b \b'*len(
                         (SIGINT_ECHO,SIGBREAK_ECHO)[sigEchoes[-1][1]==signal.SIGBREAK]
                    ))
                    sh.signaled -= 1
                    sigEchoes = sigEchoes[:-1]
                    assert sh.signaled == len(sigEchoes)    # how fast can you type ;-)
                    continue
                elif line[-1:]=='\x03': # inc case it's getting to getch
                    puts('\b \b'*len(CTRL_C_ECHO))
                else:    
                    puts('\b \b'*(len(repr(line[-1:]))-2))
                line = line[:-1]
            elif c=='\x03':
                puts(CTRL_C_ECHO)
                line += c     
            else:
                puts(repr(c)[1:-1])
                line += c     
        print '\nIts repr was %s' % `line`
        return sigEchoes
    
    # main loop
    print 'Type Ctrl-C(s) and/or Ctrl-Break(s) anywhere in input line.'
    while not sh.signaled:  # put this condition in your program loop
        
        result = dostuff()           # your stuff goes here
        
    print """\
    This should appear after dostuff() has done everything
    as if not interrupted by signals."""

    print   'Loop was terminated gracefully by %d signal%s\n' \
            '    %s\n' \
            'setting a polled variable (not via exception).' % (
                sh.signaled, 's'[sh.signaled==1:],
                ', '.join(map(lambda x: signames[x], [e[1] for e in result]))
    )

    # restore old signal handlers
    signal.signal(signal.SIGINT,old_SIGINT_Handler)
    signal.signal(signal.SIGBREAK,old_SIGBREAK_Handler)

if __name__ == '__main__':
    test()
======================================================================
Example of running this on windows with python 2.2.2:

[20:55] C:\pywk>interruptpy.py
Type Ctrl-C(s) and/or Ctrl-Break(s) anywhere in input line.
Enter a line: 123<Ctrl-C signal seen>456<Ctrl-C signal seen>789<Ctrl-Break signal seen>abc.
Its repr was '123456789abc.'
    This should appear after dostuff() has done everything
    as if not interrupted by signals.
Loop was terminated gracefully by 3 signals
    SIGINT, SIGINT, SIGBREAK
setting a polled variable (not via exception).

Regards,
Bengt Richter




More information about the Python-list mailing list