Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop an infinite loop safely in Python?

Tags:

I've got a script that runs an infinite loop and adds things to a database and does things that I can't just stop halfway through, so I can't just press Ctrl+C and stop it.

I want to be able to somehow stop a while loop, but let it finish it's last iteration before it stops.

Let me clarify:

My code looks something like this:

while True:     do something     do more things     do more things 

I want to be able to interrupt the while loop at the end, or the beginning, but not between doing things because that would be bad.

And I don't want it to ask me after every iteration if I want to continue.


Thanks for the great answers, I'm super grateful but my implementation doesn't seem to be working:

def signal_handler(signal, frame):     global interrupted     interrupted = True  class Crawler():     def __init__(self):         # not relevant      def crawl(self):         interrupted = False         signal.signal(signal.SIGINT, signal_handler)         while True:             doing things             more things              if interrupted:                 print("Exiting..")                 break 

When I press Ctrl+C the program just keeps going ignoring me.

like image 898
davegri Avatar asked Oct 03 '15 13:10

davegri


People also ask

Which keyword is used to break the infinite loop in Python?

In Python, we use the break keyword which you can see here to signal that the current loop should stop running. We can use it not only to stop infinite loops but also to stop a loop early if the code has already achieved what's needed.


1 Answers

What you need to do is catch the interrupt, set a flag saying you were interrupted but then continue working until it's time to check the flag (at the end of each loop). Because python's try-except construct will abandon the current run of the loop, you need to set up a proper signal handler; it'll handle the interrupt but then let python continue where it left off. Here's how:

import signal  import time   # For the demo only  def signal_handler(signal, frame):     global interrupted     interrupted = True  signal.signal(signal.SIGINT, signal_handler)   interrupted = False while True:     print("Working hard...")     time.sleep(3)     print("All done!")      if interrupted:         print("Gotta go")         break 

Notes:

  1. Use this from the command line. In the IDLE console, it'll trample on IDLE's own interrupt handling.

  2. A better solution would be to "block" KeyboardInterrupt for the duration of the loop, and unblock it when it's time to poll for interrupts. This is a feature of some Unix flavors but not all, hence python does not support it (see the third "General rule")

  3. The OP wants to do this inside a class. But the interrupt function is invoked by the signal handling system, with two arguments: The signal number and a pointer to the stack frame-- no place for a self argument giving access to the class object. Hence the simplest way to set a flag is to use a global variable. You can rig a pointer to the local context by using closures (i.e., define the signal handler dynamically in __init__(), but frankly I wouldn't bother unless a global is out of the question due to multi-threading or whatever.

Caveat: If your process is in the middle of a system call, handling an signal may interrupt the system call. So this may not be safe for all applications. Safer alternatives would be (a) Instead of relying on signals, use a non-blocking read at the end of each loop iteration (and type input instead of hitting ^C); (b) use threads or interprocess communication to isolate the worker from the signal handling; or (c) do the work of implementing real signal blocking, if you are on an OS that has it. All of them are OS-dependent to some extent, so I'll leave it at that.

like image 178
alexis Avatar answered Sep 16 '22 15:09

alexis