如何在 Python 中安全地停止无限循环?
How to stop an infinite loop safely in Python?
我有一个脚本,它运行一个无限循环并向数据库添加内容,并执行我不能中途停止的操作,所以我不能只按 Ctrl+C 并停止它。
我希望能够以某种方式停止 while 循环,但让它在停止之前完成最后一次迭代。
让我澄清一下:
我的代码看起来像这样:
while True:
do something
do more things
do more things
我希望能够在结束或开始时中断 while 循环,但不能在做事之间中断,因为那样会很糟糕。
而且我不希望它在每次迭代后询问我是否要继续。
感谢您的出色回答,我非常感激,但我的实施似乎没有效果:
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
当我按下 Ctrl+C 时,程序一直忽略我。
以下逻辑将帮助您做到这一点,
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")
在 运行 这个时候,尝试按 CTRL + C
希望以下代码对您有所帮助:
#!/bin/python
import sys
import time
import signal
def cb_sigint_handler(signum, stack):
global is_interrupted
print("SIGINT received")
is_interrupted = True
if __name__ == "__main__":
is_interrupted = False
signal.signal(signal.SIGINT, cb_sigint_handler)
while True:
# do stuff here
print("processing...")
time.sleep(3)
if is_interrupted:
print("Exiting..")
# do clean up
sys.exit(0)
您需要做的是捕获中断,设置一个标志说明您被打断了,然后继续工作直到检查标志(在每个循环结束时)。因为 python 的 try-except 构造会放弃当前的 运行 循环,所以你需要设置一个合适的信号处理程序;它会处理中断,然后让 python 从中断处继续。方法如下:
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
备注:
从命令行使用它。在 IDLE 控制台中,它会破坏 IDLE 自己的中断处理。
更好的解决方案是在循环期间 "block" KeyboardInterrupt,并在需要轮询中断时解除阻塞。这是一些 Unix 风格的特性,但不是全部,因此 python does not support it(参见第三个 "General rule")
OP 希望在 class 中执行此操作。但是中断函数是由信号处理系统调用的,有两个参数:信号编号和指向堆栈帧的指针——没有地方可以让 self
参数访问 class 对象。因此,设置标志的最简单方法是使用全局变量。您可以通过使用闭包(即,在 __init__()
中动态定义信号处理程序)来装配指向本地上下文的指针,但坦率地说,除非由于多线程或其他原因导致全局问题无法解决,否则我不会打扰。
警告: 如果您的进程处于系统调用的中间,处理信号可能会中断系统调用。所以这可能对所有应用程序都不安全。更安全的替代方案是 (a) 不依赖信号,而是在每次循环迭代结束时使用非阻塞读取(并键入输入而不是按 ^C); (b) 使用线程或进程间通信将 worker 与信号处理隔离开来;或者 (c) 执行实现真实 signal blocking 的工作,如果你在 OS 上的话。所有这些都在某种程度上依赖于 OS,所以我就此打住。
澄清@praba230890 的解决方案:interrupted
变量没有在正确的范围内定义。它是在 crawl
函数中定义的,根据程序根部处理程序的定义,处理程序无法将其作为全局变量访问。
这里是上述原理的编辑示例。它是单独线程中的不定式 python 循环,安全信号结束。也有线程阻塞睡眠步骤 - 由您决定是否保留它,替换为异步实现或删除。
此函数可以导入到应用程序中的任何位置,运行时不会阻塞其他代码(例如,适用于 REDIS pusub 订阅)。在 SIGINT 捕获之后,线程作业和平结束。
from typing import Callable
import time
import threading
import signal
end_job = False
def run_in_loop(job: Callable, interval_sec: int = 0.5):
def interrupt_signal_handler(signal, frame):
global end_job
end_job = True
signal.signal(signal.SIGINT, interrupt_signal_handler)
def do_job():
while True:
job()
time.sleep(interval_sec)
if end_job:
print("Parallel job ending...")
break
th = threading.Thread(target=do_job)
th.start()
您忘记在 crawl 函数中添加 global 语句。
所以结果将是
import signal
def signal_handler(signal, frame):
global interrupted
interrupted = True
class Crawler():
def __init__(self):
... # or pass if you don't want this to do anything. ... Is for unfinished code
def crawl(self):
global interrupted
interrupted = False
signal.signal(signal.SIGINT, signal_handler)
while True:
# doing things
# more things
if interrupted:
print("Exiting..")
break
我有一个脚本,它运行一个无限循环并向数据库添加内容,并执行我不能中途停止的操作,所以我不能只按 Ctrl+C 并停止它。
我希望能够以某种方式停止 while 循环,但让它在停止之前完成最后一次迭代。
让我澄清一下:
我的代码看起来像这样:
while True:
do something
do more things
do more things
我希望能够在结束或开始时中断 while 循环,但不能在做事之间中断,因为那样会很糟糕。
而且我不希望它在每次迭代后询问我是否要继续。
感谢您的出色回答,我非常感激,但我的实施似乎没有效果:
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
当我按下 Ctrl+C 时,程序一直忽略我。
以下逻辑将帮助您做到这一点,
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")
在 运行 这个时候,尝试按 CTRL + C
希望以下代码对您有所帮助:
#!/bin/python
import sys
import time
import signal
def cb_sigint_handler(signum, stack):
global is_interrupted
print("SIGINT received")
is_interrupted = True
if __name__ == "__main__":
is_interrupted = False
signal.signal(signal.SIGINT, cb_sigint_handler)
while True:
# do stuff here
print("processing...")
time.sleep(3)
if is_interrupted:
print("Exiting..")
# do clean up
sys.exit(0)
您需要做的是捕获中断,设置一个标志说明您被打断了,然后继续工作直到检查标志(在每个循环结束时)。因为 python 的 try-except 构造会放弃当前的 运行 循环,所以你需要设置一个合适的信号处理程序;它会处理中断,然后让 python 从中断处继续。方法如下:
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
备注:
从命令行使用它。在 IDLE 控制台中,它会破坏 IDLE 自己的中断处理。
更好的解决方案是在循环期间 "block" KeyboardInterrupt,并在需要轮询中断时解除阻塞。这是一些 Unix 风格的特性,但不是全部,因此 python does not support it(参见第三个 "General rule")
OP 希望在 class 中执行此操作。但是中断函数是由信号处理系统调用的,有两个参数:信号编号和指向堆栈帧的指针——没有地方可以让
self
参数访问 class 对象。因此,设置标志的最简单方法是使用全局变量。您可以通过使用闭包(即,在__init__()
中动态定义信号处理程序)来装配指向本地上下文的指针,但坦率地说,除非由于多线程或其他原因导致全局问题无法解决,否则我不会打扰。
警告: 如果您的进程处于系统调用的中间,处理信号可能会中断系统调用。所以这可能对所有应用程序都不安全。更安全的替代方案是 (a) 不依赖信号,而是在每次循环迭代结束时使用非阻塞读取(并键入输入而不是按 ^C); (b) 使用线程或进程间通信将 worker 与信号处理隔离开来;或者 (c) 执行实现真实 signal blocking 的工作,如果你在 OS 上的话。所有这些都在某种程度上依赖于 OS,所以我就此打住。
澄清@praba230890 的解决方案:interrupted
变量没有在正确的范围内定义。它是在 crawl
函数中定义的,根据程序根部处理程序的定义,处理程序无法将其作为全局变量访问。
这里是上述原理的编辑示例。它是单独线程中的不定式 python 循环,安全信号结束。也有线程阻塞睡眠步骤 - 由您决定是否保留它,替换为异步实现或删除。 此函数可以导入到应用程序中的任何位置,运行时不会阻塞其他代码(例如,适用于 REDIS pusub 订阅)。在 SIGINT 捕获之后,线程作业和平结束。
from typing import Callable
import time
import threading
import signal
end_job = False
def run_in_loop(job: Callable, interval_sec: int = 0.5):
def interrupt_signal_handler(signal, frame):
global end_job
end_job = True
signal.signal(signal.SIGINT, interrupt_signal_handler)
def do_job():
while True:
job()
time.sleep(interval_sec)
if end_job:
print("Parallel job ending...")
break
th = threading.Thread(target=do_job)
th.start()
您忘记在 crawl 函数中添加 global 语句。 所以结果将是
import signal
def signal_handler(signal, frame):
global interrupted
interrupted = True
class Crawler():
def __init__(self):
... # or pass if you don't want this to do anything. ... Is for unfinished code
def crawl(self):
global interrupted
interrupted = False
signal.signal(signal.SIGINT, signal_handler)
while True:
# doing things
# more things
if interrupted:
print("Exiting..")
break