Python 通过 REPL 与阻塞循环交互的最佳实践

Python best practice for interacting with blocking loop via REPL

我的程序涉及阻塞循环。我想在程序处于 运行ning.

时使用 python REPL 修改程序状态

上下文:

我的 python 代码中经常有一个阻塞循环:

# main.py
import itertools
import time
global_state = 123

if __name__ == "__main__":
    print("Starting main loop")
    m = 0
    for n in itertools.count():
        time.sleep(1)  # "computation"
        global_state += 1
        m += 10
        print(f"{n=}, {m=}, {global_state=}")

当我在命令行 运行 这个程序时,我得到这样的东西:

$ python -i main.py
Starting main loop
n=0, m=10, global_state=124
n=1, m=20, global_state=125
n=2, m=30, global_state=126
...

循环将 运行 数小时或数天。有时,在循环 运行ning 期间,我希望以交互方式修改某些程序状态。例如,我想设置 global_state = -1000m = 75。但是 loop 函数正在阻塞...

交互式修改程序状态的方法

方法 1:停止并重新启动循环

我可以使用 KeyboardInterrupt 来停止循环。由于我已经使用 -i 交互式标志调用了 python,因此我可以在交互式 REPL 中修改全局状态。然后我重新启动循环。这是一个简单的方法,但它有缺点:

方法 2:使用线程

我可以将循环包装在一个函数中,然后使用 threading 到 运行 另一个线程中的函数:

def loop():
    print("Starting main loop")
    m = 0
    for n in itertools.count():
        time.sleep(1)  # "computation"
        global_state += 1
        m += 10
        print(f"{n=}, {m=}, {global_state=}")
import threading
thread = threading.Thread(target=loop)
thread.start()

当我调用 python -i main.py 时,我可以使用 REPL 与全局状态交互,作为另一个线程中的循环 运行。 loop 函数仍然像以前一样从 printstdout。这种方法的缺点是:

方法 3:从循环内进入 REPL

可以配合循环掉入repl:

import os
import code
if __name__ == "__main__":
    ...
    for n in itertools.count():
        ...  # business logic
        if os.stat("use_repl.txt").st_size > 0:  # file nonempty             
            code.interact(local=locals())

这里我们使用一个文件在我们想要修改状态时向循环发出信号。如果“use_repl.txt”文件是非空的,那么循环然后调用 code.interact 来创建一个交互式控制台。这有一个额外的好处,即使循环被包裹在一个函数中也能正常工作;局部变量被加载到交互式控制台中。 缺点:

调用 code.interact 的替代方法是调用 breakpoint 内置函数。另一种选择是调用 break 关键字,退出循环。然后可以修改状态并重新启动循环(如方法 1 中所示),而不必担心 KeyboardInterrupt 会创建不一致的状态。

问题:

有没有我错过的方法?所考虑的每种方法都有明显的缺点......是否有关于实现预期结果的最佳实践?

调试程序,当你想要inspect/modify状态时暂停它,然后恢复。

受 xaa 的启发 :

import pdb
import signal
signal.signal(signal.SIGINT, pdb.Pdb().sigint_handler)

这会设置一个信号处理程序来捕获 SIGINT(否则会被翻译成 KeyboardInterrupt),并在按下 CTRL-C 时掉落到 pdb。因此 CTRL-C 可用于中断循环并在任何时候检查或修改状态,并且可以通过在 pdb 提示符下键入 continue 来恢复执行。