2

I have a python script running over some 50,000 items in a database, it takes about 10 seconds per item and I want to stop it.

But, if i just press ctrl + C while the code is in the try except part, then my program enter the expect statement and delete that item then continue. The only way to close the program is to repeatedly delete items this way until I, just be sheer luck, catch it not in the try statement.

How do I exit the script without just killing a try statement?

EDIT 1

I have a statement that looks something like this:

for item in items:
   try:
      if is_item_gone(content) == 1:
         data = (item['id'])
         db.update('UPDATE items SET empty = 0 WHERE id = (%s);', data)
      else:
          do alot of stuff
   except:
      data = (item['id'])
      db.delete('...')

EDIT 2

The top of my connect to db code looks like:

#!/usr/bin/python
import MySQLdb
import sys

class Database:
....
Rorschach
  • 3,684
  • 7
  • 33
  • 77
  • Do `except Exception` instead of `except`. – univerio May 25 '16 at 22:15
  • What exception(s) are you catching? – Padraic Cunningham May 25 '16 at 22:15
  • 2
    The issue is almost certainly because you are using a blanket *except* which is almost always a bad idea, if you catch specific exceptions then ctrl + C will stop your program – Padraic Cunningham May 25 '16 at 22:32
  • @PadraicCunningham I have my code added now. – Rorschach May 25 '16 at 22:45
  • Yes, your blanket except is the issue, what are you using to connect to the db? Basically you should have `except some_specific_exception` or `except some_specific_exception, another_specific_exception`, because you are catching every exception you catch the keyboard interrupt so the script keeps running – Padraic Cunningham May 25 '16 at 22:47
  • @PadraicCunningham mysqldb – Rorschach May 25 '16 at 22:53
  • not to be a grammar nazi but it should be 'except (some_specific_exc, another_specific_exc)' or it will interpreted as 'except Exception e' with 'another_specific_exc' never caught – simone cittadini May 25 '16 at 22:54
  • no I'm referring to @Padraic comment , if you put two exception types after an exception statement without enclosing them in a list the second one will be interpreted like a variable and never caught. ( python grammar, not human grammar) – simone cittadini May 25 '16 at 23:10

3 Answers3

3

The issue is because you are using a blanket except which is always a bad idea, if you catch specific exceptions then when you KeyboardInterrupt your script will stop:

for item in items:
   try:
      if is_item_gone(content) == 1:
         data = (item['id'])
         db.update('UPDATE items SET empty = 0 WHERE id = (%s);', data)
      else:
          do alot of stuff
   except MySQLdb.Error  as e:
      print(e)
      data = (item['id'])
      db.delete('...')

If you have other exceptions to catch you can use multiple in the except:

 except (KeyError, MySQLdb.Error) as e

At the very least you could catch Exception as e and print the error.

for item in items:
   try:
      if is_item_gone(content) == 1:
         data = (item['id'])
         db.update('UPDATE items SET empty = 0 WHERE id = (%s);', data)
      else:
          do alot of stuff
   except Exception as e:  
      print(e)
      data = (item['id'])
      db.delete('...')

The moral of the story is don't use a blanket except, catch what you expect and logging the errors might also be a good idea. The exception-hierarchy is also worth reading to see exactly what you are catching.

Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
1

I would rewrite it like this:

try:
    # ...
except KeyboardInterrupt:
    sys.exit(1)
except Exception:  # I would use a more specific exception here if you can.
    data = (item['id'])
    db.delete('...')

Or just

try:
    # ...
except Exception:  # I would use an even more specific exception here if you can.
    data = (item['id'])
    db.delete('...')

Python 2's exception hiearchy can be found here: https://docs.python.org/2/library/exceptions.html#exception-hierarchy

Also, many scripts are written with something like this boiler plate at the bottom:

def _main(args):
   ...

if __name__ == '__main__':
    try:
        sys.exit(_main(sys.argv[1:]))
    except KeyboardInterrupt:
        print >> sys.stderr, '%s: interrupted' % _SCRIPT_NAME
        sys.exit(1)

When you use except: instead of except Exception: in your main body, you are not allowing this top level exception block to catch the KeyboardInterrupt. (You don't have to actually catch it for Ctrl-C to work -- this just makes it prettier when KeyboardInterrupt is raised.)

rrauenza
  • 6,285
  • 4
  • 32
  • 57
0
import signal
import MySQLdb
import sys

## Handle process signals:
def sig_handler(signal, frame):
    global connection
    global cursor

    cursor.close()
    connection.close()

    exit(0)

## Register a signal to the handler:
signal.signal(signal.SIGINT, sig_handler)

class Database:
    # Your class stuf here.
    # Note that in sig_handler() i close the cursor and connection,
    # perhaps your class has a .close call? If so use that.

for item in items:
   try:
      if is_item_gone(content) == 1:
         data = (item['id'])
         db.update('UPDATE items SET empty = 0 WHERE id = (%s);', data)
      else:
          do alot of stuff
   except MySQLdb.Error  as e:
      data = (item['id'])
      db.delete('...')

This would register and catch for instance Ctrl+c at any given time. The handler would terminate a socket accordingly, then exit with exit code 0 which is a good exit code.

This is a more "global" approach to catching keyboard interrupts.

Torxed
  • 22,866
  • 14
  • 82
  • 131
  • If you're spending time to down vote stuff, be a good sport and explain why. Some of us are actually trying to improve and it's hard to do so without proper feedback. (Noticed the down vote is gone now, but if you're reading this or planning on down voting, do write a note). – Torxed May 25 '16 at 22:29
  • I didn't downvote you, but I think your solution is not seen as very pythonic. First, the OP should not be catching all exceptions, but rather should be more specific. Second, there is an exception to catch for Ctrl-C, which would keep the Ctrl-C handling code local to the block you are catching it in: http://stackoverflow.com/questions/15318208/capture-control-c-in-python – rrauenza May 25 '16 at 22:55
  • 1
    @rrauenza I guess technically this isn't "Pythonic" but it's thread friendly and *nix standard way of catching events such as Ctrl+C (which, in fact, is a signal to terminate and nothing else). `try` should in my humble opinion be reserved for either isolated events or for actual code issues, not user/external input. But then again, People seem to like `try` catches these days just as much as people liked `goto` back in the Visual Basic days. I dunno, I'm getting old and grumpy I guess. Anyway, question answered and marked as solved, work here is done I guess. – Torxed May 25 '16 at 23:06
  • @Rorschach Yes, This code should be at the top of your script to catch any kind of "signal to terminate". It's quite useful to stop threads safely, terminate sockets without having to wrap a huge block of code (while loops etc) in a ugly `try` catch. Try catches are good, but use a proper method for different situations. In this case you want to catch actual SIGHUP signals (Ctrl+C) which is a OS defined user input to terminate a script. Sure there's a `try` catch for it, but in all its essense it's a signal, treat it as such. – Torxed May 25 '16 at 23:09
  • 1
    Note that you don't actually have to catch `KeyboardInterrupt` for Ctrl-C to work ... you have to not *accidentally* catch it with a blanket `except:`. – rrauenza May 25 '16 at 23:12