【问题标题】:How to stop an infinite loop safely in Python?如何在 Python 中安全地停止无限循环?
【发布时间】:2015-12-31 14:14:04
【问题描述】:

我有一个脚本,它运行一个无限循环并将​​内容添加到数据库中并执行一些我不能在中途停止的事情,所以我不能只是按 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 时,程序会一直忽略我。

【问题讨论】:

  • 你有没有办法在你的脚本中确定工作是否已经完成(最后一个)!如果你这样做,那么在条件中使用它并使用 break 语句
  • 在这种情况下的工作是一个网络爬虫,可以无限期地继续下去。我希望能够告诉它停止爬行,但不仅仅是在页面中间中断它。
  • 让它在它停止之前完成它的最后一次迭代?你怎么知道它是否完成了这项工作?
  • 嗯...您可以添加脚本的重要部分吗?这可能有助于其他人了解您想要什么:P
  • 问题很明确:完成这个循环的迭代,然后退出。谁认为这是题外话?

标签: python infinite-loop


【解决方案1】:

你需要做的是捕捉中断,设置一个标志,表明你被中断,然后继续工作,直到检查标志的时候(在每个循环结束时)。因为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

注意事项:

  1. 从命令行使用它。在 IDLE 控制台中,它会践踏 IDLE 自己的中断处理。

  2. 更好的解决方案是在循环期间“阻塞”KeyboardInterrupt,并在轮询中断时解除阻塞。这是一些 Unix 风格的特性,但不是全部,因此 python does not support it(参见第三条“一般规则”)

  3. OP 想要在类中执行此操作。但是中断函数由信号处理系统调用,带有两个参数:信号号和指向堆栈帧的指针——没有self 参数可以访问类对象。因此,设置标志的最简单方法是使用全局变量。您可以通过使用闭包来装配指向本地上下文的指针(即,在__init__() 中动态定义信号处理程序,但坦率地说,除非由于多线程或其他原因而无法使用全局变量,否则我不会打扰。

警告:如果您的进程处于系统调用的中间,处理信号可能会中断系统调用。因此,这可能对所有应用程序都不安全。更安全的替代方案是 (a) 在每次循环迭代结束时使用非阻塞读取而不是依赖信号(并键入输入而不是按 ^C); (b) 使用线程或进程间通信将工作人员与信号处理隔离开来;或 (c) 如果您使用的是具有它的操作系统,则执行实现真正的signal blocking 的工作。它们都在一定程度上依赖于操作系统,所以我就这样吧。

【讨论】:

  • 好的,我想我明白了,但是我的函数在一个类中运行,所以我需要将 signal_handler 定义为类的函数吗?
  • 信号处理程序在正常的控制流程之外被调用。您不需要 将其设为类方法,而且我不确定它是否会起作用。查看signal 文档...
  • 简短回答:只需使用顶级函数和全局变量即可。除非您是多线程等,否则生活会变得困难......
  • 你能看看我的编辑吗?我的实现不起作用,我现在编辑了我的帖子以包含它。
  • 很容易发现:crawl() 正在设置,然后检查一个 local 变量 interrupted :-) 它需要是全局的,以便中断处理程序可以访问它。
【解决方案2】:

以下逻辑将帮助您做到这一点,

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

【讨论】:

    【解决方案3】:

    希望下面的代码对你有所帮助:

    #!/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(1):
            # do stuff here 
            print "processing..."
            time.sleep(3)
            if is_interrupted:
                print "Exiting.."
                # do clean up
                sys.exit(0)
    

    【讨论】:

      【解决方案4】:

      澄清@praba230890 的解决方案:interrupted 变量未在正确的范围内定义。它是在crawl 函数中定义的,根据程序根部处理程序的定义,处理程序无法将其作为全局变量访问。

      【讨论】:

        【解决方案5】:

        这里是上述原理的编辑示例。它是独立线程中的不定式 python 循环,安全信号结束。还具有线程阻塞睡眠步骤 - 由您决定保留它,替换为 asyncio 实现或删除。 这个函数可以被导入到应用程序的任何地方,运行时不会阻塞其他代码(例如,适合 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()
        

        【讨论】:

          【解决方案6】:

          您忘记在 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
          

          【讨论】:

          • 无需进行任何重大更改。只需增加一行(如果您没有导入 signal,则为 2)
          猜你喜欢
          • 1970-01-01
          • 2021-05-06
          • 2013-11-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-05
          • 1970-01-01
          相关资源
          最近更新 更多