Trapping CTRL+C in Python

1ebeb1d2f212046a4e47fcd414dbad9b1I’ve been doing some nasty database work with Python and MySQL.  Specifically, I’ve got long programs that run eight to ten hours, and sometimes I need to kill them and restart them later.

So… I wanted a way to trap CTRL+C and clean up.  That is, I wanted to have transactions without actually having to use transactions.  I’m funny that way.  (In other news, I’m probably moving to PostgreSQL at some point.)

Anyway, here’s what I came up with.  You might find it useful, too.

#!/usr/bin/python -i
 
'''
Trap keyboard interrupts.  No rights reserved; use at your own risk.
 
@author: Stacy Prowell (http://stacyprowell.com)
'''
import signal
 
class BreakHandler:
    '''
    Trap CTRL-C, set a flag, and keep going.  This is very useful for
    gracefully exiting database loops while simulating transactions.
 
    To use this, make an instance and then enable it.  You can check
    whether a break was trapped using the trapped property.
 
    # Create and enable a break handler.
    ih = BreakHandler()
    ih.enable()
    for x in big_set:
        complex_operation_1()
        complex_operation_2()
        complex_operation_3()
        # Check whether there was a break.
        if ih.trapped:
            # Stop the loop.
            break
    ih.disable()
    # Back to usual operation...
    '''
 
    def __init__(self, emphatic=9):
        '''
        Create a new break handler.
 
        @param emphatic: This is the number of times that the user must
                    press break to *disable* the handler.  If you press
                    break this number of times, the handler is automagically
                    disabled, and one more break will trigger an old
                    style keyboard interrupt.  The default is nine.  This
                    is a Good Idea, since if you happen to lose your
                    connection to the handler you can *still* disable it.
        '''
        self._count = 0
        self._enabled = False
        self._emphatic = emphatic
        self._oldhandler = None
        return
 
    def _reset(self):
        '''
        Reset the trapped status and count.  You should not need to use this
        directly; instead you can disable the handler and then re-enable it.
        This is better, in case someone presses CTRL-C during this operation.
        '''
        self._count = 0
        return
 
    def enable(self):
        '''
        Enable trapping of the break.  This action also resets the
        handler count and trapped properties.
        '''
        if not self._enabled:
            self._reset()
            self._enabled = True
            self._oldhandler = signal.signal(signal.SIGINT, self)
        return
 
    def disable(self):
        '''
        Disable trapping the break.  You can check whether a break
        was trapped using the count and trapped properties.
        '''
        if self._enabled:
            self._enabled = False
            signal.signal(signal.SIGINT, self._oldhandler)
            self._oldhandler = None
        return
 
    def __call__(self, signame, sf):
        '''
        An break just occurred.  Save information about it and keep
        going.
        '''
        self._count += 1
        # If we've exceeded the "emphatic" count disable this handler.
        if self._count >= self._emphatic:
            self.disable()
        return
 
    def __del__(self):
        '''
        Python is reclaiming this object, so make sure we are disabled.
        '''
        self.disable()
        return
 
    @property
    def count(self):
        '''
        The number of breaks trapped.
        '''
        return self._count
 
    @property
    def trapped(self):
        '''
        Whether a break was trapped.
        '''
        return self._count > 0

To use this, make an instance and then enable the instance.  If you repeatedly hit CTRL+C you will eventually disable it (see emphatic in the __init__).

# Trap break.
bh = BreakHandler()
bh.enable()
for item in big_set:
    if bh.trapped:
        print 'Stopping at user request (keyboard interrupt)...'
        break
    do_thing_1()
    do_thing_2()
    do_thing_3()
do_cleanup()
bh.disable()

If you press CTRL+C, it is detected at the start of the loop, and break is called.  Then do_cleanup() runs, and the break handler is disabled, returning things to normal.  The idea is that do_thing_1(), do_thing_2(), and do_thing_3() all run to completion, even when the user presses CTRL+C, and do_cleanup() always runs when the loop is exited.

I consider this public domain; if you like it, use it. If you don’t like it, don’t use it. If you have suggestions, please leave me a comment.

Tagged , , , ,

10 thoughts on “Trapping CTRL+C in Python

  1. Brendan says:

    You should add a thing to let you specify a callback when the user hits ctrl-c. As long as the callback returns, there’s no difference.

    • stacy says:

      That’s easy to do. There’s not much code; feel free to make the change! You could also modify the code to allow trapping other signals. I think on Windows the Break key generates a different signal than CTRL+C.

  2. aonlazio says:

    Superb, this is exactly what I want and I bet most people in network programming realm want too.
    By the way I have a couple of questions
    (1) Why do u use “_” in front of some properties such as self._count, self._enabled, self._emphatic ? Does it have special meanings?

    (2) What does @property do?

    Thank you very much.

    Aonlazio

  3. stacy says:

    The single underscore signals that something is private “by convention.” That is, you can still access the item, but the underscore signals that you shouldn’t.

    From PEP 8 (http://www.python.org/dev/peps/pep-0008/):

    _single_leading_underscore: weak “internal use” indicator. E.g. “from M import *” does not import objects whose name starts with an underscore.

    The @property basically makes a read-only attribute. That is, I can write:

    bh = BreakHandler()

    print bh.trapped
    print bh.count

    But I cannot set them with bh.trapped = True.

    The @property is a decorator providing a shorthand for the python property() built in to declare a read-only property. Python decorators are pretty nifty. They are described in PEP 318 (http://www.python.org/dev/peps/pep-0318/).

    See http://www.python.org/doc/2.6/library/functions.html and search for @property.

  4. demikaze says:

    ctrl-c generates KeyboardInterrupt exception… ;)

  5. Fantastic post, I will save this post in my Del.icio.us account. Have a great evening.

  6. Yuki says:

    Hi.

    This is exactly what I need. I implemented everything like you said but when I run it, it is as if there’s a flag implemented even before I can implement the ctrl-c. Thus my loop only goes through 1 cycle.

    Where does do_cleanup come from and is it necessary?

  7. Hi, my english isnt very best but I believe by regulary visits of this blog it will be far better within the next time. You have a very good wrting style that is simple to understand and can assists men and women like me to learn english. I will be now a regulary visitor of your blog.

  8. Drip Tray says:

    .”` I am very thankful to this topic because it really gives great information :’`

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>