Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python通过REPL与阻塞循环交互的最佳实践_Python_Loops_Interactive_Read Eval Print Loop - Fatal编程技术网

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

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():

我的程序包含一个阻塞循环。我想在程序运行时使用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
...
循环将运行数小时或数天。有时,在循环运行时,我希望以交互方式修改某些程序状态。例如,我想设置
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)