Stupid Python Tricks: The KeyboardInterrupt Exception

If you try to stop a CPython program using Control-C, the interpreter throws a KeyboardInterrupt exception.

Unfortunately, this is an ordinary exception, and is, like all other exceptions, caught by a “catch-all” try-except statement.

try:
    # do something
except:
    # you'll end up here if something goes wrong,
    # or if the user presses control-c

For example, if your program contains code like the following, your users may find that pressing Control-C is a great way to mess up their database, but a really lousy way to stop the program:

for record in database:
    try:
        process(record)
        if changed:
            update(record)
    except:
        # report error and proceed

To solve the exception problem, add a separate except-clause that catches the KeyboardInterrupt exception, and raises it again:

for record in database:
    try:
        process(record)
        if changed:
            update(record)
    except KeyboardInterrupt:
        raise
    except:
        # report error and proceed

or, even better:

for record in database:
    try:
        process(record)
        if changed:
            update(record)
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        # report error and proceed

By looking for SystemExit as well, calling sys.exit() in the processing or update code will actually stop the program.

Note that if the update process isn’t an atomic operation in itself, it’s also a good idea to use database transactions, and roll back when something goes wrong:

for record in database:
    begin()
    try:
        process(record)
        if changed:
            update(record)
    except (KeyboardInterrupt, SystemExit):
        rollback()
        raise
    except:
        rollback()
        # report error and proceed
    else:
        commit()