Python通过REPL与阻塞循环交互的最佳实践
我的程序包含一个阻塞循环。我想在程序运行时使用python REPL修改程序状态 背景: 我的python代码中通常有一个阻塞循环:Python通过REPL与阻塞循环交互的最佳实践,python,loops,interactive,read-eval-print-loop,Python,Loops,Interactive,Read Eval Print Loop,我的程序包含一个阻塞循环。我想在程序运行时使用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():
# 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
...
循环将运行数小时或数天。有时,在循环运行时,我希望以交互方式修改某些程序状态。例如,我想设置global\u state=-1000
或m=75
。但是循环
函数正在阻塞
程序状态的交互修改方法
方法1:停止并重新启动循环
我可以使用键盘中断来停止循环。由于我已使用-I
交互式标志调用了python
,因此我可以在交互式REPL处修改全局状态。然后重新启动循环。这是一种简单的方法,但也有缺点:
- 如果循环修改全局状态(或有任何副作用),则在引发键盘中断时,全局状态可能处于不一致状态;异常可以在循环控制流中的任何一点引发,正确处理该异常可能很棘手,尤其是在业务逻辑复杂的情况下
- 重新启动循环时,计数器
n
将重置为零。循环使用的其他状态变量(例如,m
)也可能被重置或处于不一致状态
方法2:使用线程
我可以将循环包装在一个函数中,并使用线程
在另一个线程中运行该函数:
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与全局状态交互。循环
功能仍然像以前一样将打印到stdout
。这种方法的缺点是:
- 我不能再与循环的局部状态交互(例如,我不能修改
m
),因为局部状态被包装在函数中。这是一个主要缺点
- 使用线程使程序更加脆弱;线程可能会崩溃,线程中引发的异常需要小心处理。如果线程阻塞、崩溃或挂起,则很难恢复
- 停止循环变得更加困难,因为KeyboardInterrupt不再是一个选项(相反,我需要与线程“合作”,向它发送一些应该自动终止的信号)
方法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
以创建交互式控制台。即使循环被包装在一个函数中,这也会增加工作的好处;本地变量被加载到交互式控制台中。
缺点:
- 每次通过循环读取“use_repl.txt”文件时都会感觉有点笨拙。但除此之外,如何(协同地)向循环发出信号,要求它创建一个REPL呢
- 调用
code.interact
时,循环暂停。这与基于线程的解决方案不同,后者循环连续运行。这可能是优点也可能是缺点,这取决于您的用例
- 由
code.interact
创建的REPL没有python的本机交互模式那么完美。例如,制表符完成不再有效
调用code.interact
的替代方法是调用断点
内置函数。另一种选择是调用break
关键字,退出循环。然后可以修改状态并重新启动循环(如方法1所示),而不必担心键盘中断造成了不一致的状态
问题:
我有没有错过什么方法?考虑的每种方法都有明显的缺点。。。是否有关于实现预期结果的最佳实践?调试程序,在需要检查/修改状态时暂停,然后继续。受xaa的启发:
这将设置一个信号处理程序捕捉SIGINT
(否则将被转换为KeyboardInterrupt
),当按下CTRL-C
时,该处理程序将进入pdb
。因此,CTRL-C
可以用来中断循环并在任意点检查或修改状态,并且可以通过在pdb
提示符下键入continue
来恢复执行。这是一种非常开放的、基于意见的方式,但如果我想使其具有可编辑状态,我会让Web服务器更改状态,当工人在线程中运行时,这是有道理的。我一直回避使用数据库/服务器来跟踪状态,因为在我的特定用例中,有时很难预测我想要更改状态的哪些部分。有时我甚至会更改循环执行的指令,可能会引入新的状态变量或有状态代码。
import pdb
import signal
signal.signal(signal.SIGINT, pdb.Pdb().sigint_handler)