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:
Use this from the command line. In the IDLE console, it'll trample on IDLE's own interrupt handling.
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")
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
selfargument 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.
Answer from alexis on Stack Overflow[Thought experiment] Achieving one-line infinite loops in Python
"for i in range()" to do an infinite loop with a counter - Ideas - Discussions on Python.org
How to stop an infinite loop safely in Python? - Stack Overflow
How do you make an infinite printing loop in python? - Stack Overflow
Videos
DISCLAIMER: This post is mainly just curious thoughts, it has nothing to do with real-life application or good practice. So please don't actually use any examples provided.
Python is (or at least was) rather famous for its possibilities for one-liners (programs occupying only a single line of code) some time ago. A lot of things can be achieved like this, but among the most puzzling things must be infinite loops; they aren't exactly easy to implement with the tools we have available.
An infinite loop usually requires the use of a while-loop, because for-loops have a beginning and an end. Using a while-loop in one-liners is problematic, though, because you may only use it once, on the top level. This is due to how Python restricts block structures to either be separated by whitespace (and proper indentation), or to only have a single depth level following it. In other words,
while True: print("This works!")is valid Python, but
while True: if 1 == 1: print("But this doesn't...)is not.
We do have another "kind" of loop, though; list comprehensions. They are unique in that they may be nested as we see fit, all while using only a single line.
[["Order pizza." for _ in range(6)] for _ in range(42)]
But this doesn't give us an infinite loop; even if we simply input a ridiculously large number to range, it's still technically finite no matter what kind of hardware we're using. Thus, a different approach is required. I mentioned how infinite loops usually require the use of while-loops in Python. We can, however, utilise a certain property of Python to create an infinite loop with for-loops.
nums = [1, 2, 3, 4]
for num in nums:
print(num)Okay, that prints out four numbers. Not exactly infinite. But if we tweak our approach a little...
nums = [1]
for num in nums:
print(num)
nums.append(num + 1)We actually get... as many numbers as the computer's memory allows. With this, we can essentially get something like this to work:
nums=[1];[(print(num) and nums.append(num+1)) for num in nums]
(Disclaimer; I never tested if that actually runs.)
It's not a pure one-liner, because it still technically requires two lines (fused together with a semicolon), but it's a proof-of-concept. I initially tried to make it work without having to define a variable, but failed to find a way.
I hope this was mildly interesting, I don't usually write stuff like this. Just found it curious myself, so why not share the thought? Maybe someone can even improve on this.
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:
Use this from the command line. In the IDLE console, it'll trample on IDLE's own interrupt handling.
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")
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
selfargument 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.
the below logic will help you do this,
import signal
import sys
import time
run = True
def signal_handler(signal, frame):
global run
print("exiting")
run = False
signal.signal(signal.SIGINT, signal_handler)
while run:
print("hi")
time.sleep(1)
# do anything
print("bye")
while running this, try pressing CTRL + C
You can use the second argument of iter(), to call a function repeatedly until its return value matches that argument. This would loop forever as 1 will never be equal to 0 (which is the return value of int()):
for _ in iter(int, 1):
pass
If you wanted an infinite loop using numbers that are incrementing you could use itertools.count:
from itertools import count
for i in count(0):
....
The quintessential example of an infinite loop in Python is:
while True:
pass
To apply this to a for loop, use a generator (simplest form):
def infinity():
while True:
yield
This can be used as follows:
for _ in infinity():
pass
Using itertools.count:
import itertools
for i in itertools.count(start=1):
if there_is_a_reason_to_break(i):
break
In Python 2, range() and xrange() were limited to sys.maxsize. In Python 3 range() can go much higher, though not to infinity:
import sys
for i in range(sys.maxsize**10): # you could go even higher if you really want
if there_is_a_reason_to_break(i):
break
So it's probably best to use count().
def to_infinity():
index = 0
while True:
yield index
index += 1
for i in to_infinity():
if i > 10:
break
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)With for w in words:, the example would attempt to create an infinite list, inserting defenestrate over and over again. I don't understand why the for-loop does it; shouldn't it exit from the loop when it comes to the value defenestrate and the code below the if statement is executed since it evaluates to True.
If the statement was while without any break statement I would understand, but with a for-loop I don't understand. Why is there no end when it doesn't have to go through an infinite list but only a list with 3 values.
In contrast, If I just change the line for w in words: to for w in words[:]: the loop will no longer be infinite and the output will be ['defenestrate', 'cat', 'window', 'defenestrate'] Why is that so?
I am a beginner in programming and would be grateful if you could answer with no advanced terms. Thx.
The example itself can be seen in the Python documentation 3.7.2 under Tutorials, under More Control Flow Tools, under For Statements.