如何使python脚本可以从另一个脚本停止?

如何使python脚本可以从另一个脚本停止?,python,Python,TL;DR:如果您有一个程序应该运行一段不确定的时间,那么当用户决定是时候时,您如何编写代码来停止它?(无键盘中断或终止任务) -- 我最近发布了这个问题: 答案确实解决了我的问题,但从终止/中断的角度来看,这并不是我真正想要的。(虽然,我的问题没有说清楚) 所以,我要重新措辞 出于示例目的,我创建了一个通用脚本。所以我有一个类,它从通用API收集数据,并将数据写入csv。通过在终端窗口上键入python main.py来启动代码 import time,csv import GenericA

TL;DR:如果您有一个程序应该运行一段不确定的时间,那么当用户决定是时候时,您如何编写代码来停止它?(无键盘中断或终止任务)

--

我最近发布了这个问题: 答案确实解决了我的问题,但从终止/中断的角度来看,这并不是我真正想要的。(虽然,我的问题没有说清楚) 所以,我要重新措辞

出于示例目的,我创建了一个通用脚本。所以我有一个类,它从通用API收集数据,并将数据写入csv。通过在终端窗口上键入
python main.py
来启动代码

import time,csv

import GenericAPI

class GenericDataCollector:
    def __init__(self):
        self.generic_api = GenericAPI()
        self.loop_control = True

    def collect_data(self):
        while self.loop_control: #Can this var be changed from outside of the class? (Maybe one solution)
            data = self.generic_api.fetch_data() #Returns a JSON with some data
            self.write_on_csv(data)
            time.sleep(1)

    def write_on_csv(self, data):
        with open('file.csv','wt') as f:
            writer = csv.writer(f)
            writer.writerow(data)

def run():
    obj = GenericDataCollector()
    obj.collect_data()

if __name__ == "__main__":
    run()
脚本应该永远运行,或者直到我命令它停止。我知道我可以用键盘中断(Ctrl+C)或者突然终止任务这不是我想要的。我想要一种“软”的方式来告诉脚本该停止了,这不仅是因为中断可能是不可预测的,也是一种苛刻的停止方式

例如,如果该脚本在docker容器上运行,则除非您恰好位于docker中的terminal/bash中,否则您将无法
Ctrl+C

或者另一种情况:如果该脚本是为客户编写的,我不认为可以告诉客户,只需使用
Ctrl+C
/停止任务即可。绝对违反直觉,特别是如果是非技术人员

我正在寻找编写另一个脚本的方法(假设这是一个可能的解决方案),该脚本将更改为
False
属性
obj.loop\u control
,在完成循环后完成循环。可以通过在(不同的)终端上键入python stop\u script.py来运行的东西

不一定要这样。其他解决方案也可以接受,只要不涉及键盘中断或终止任务。如果我能在类中使用一个方法,那就太好了,只要我能从另一个终端/脚本调用它

有办法做到这一点吗


如果你有一个程序应该运行一段不确定的时间,当用户决定时间到了,你如何编写代码来停止它

您真正想要的是从一个程序向另一个独立程序发送信号的任何方式。一种方法是使用进程间管道。(无可否认,这似乎需要一个兼容POSIX的shell,但大多数主要操作系统都应该提供)

您需要做的是在运行的程序(比如说
main.py
)和停止的程序(比如说
stop.sh
)之间预先商定一个文件路径。然后,您可以让主程序运行,直到有人向该管道输入某些内容:

import pipes
...
t = pipes.Template()
# create a pipe in the first place
t.open("/tmp/pipefile", "w")
# create a lasting pipe to read from that
pipefile = t.open("/tmp/pipefile", "r")
...
现在,在程序内部,将循环条件更改为“只要此文件没有输入-除非有人向其写入内容,
.read()
将返回一个空字符串:

while not pipefile.read():
    # do stuff
要停止它,请放入另一个文件或脚本或将写入该文件的内容。这是使用shell脚本最容易做到的:

#!/usr/bin/env sh
echo STOP >> /tmp/pipefile
如果您要将其容器化,您可以将其放入
/usr/bin
并命名为
stop
,给它至少
0111
权限,并告诉用户“要停止程序,只需执行
docker exec containername stop

(使用
>
而不是
非常重要,因为我们只想附加到管道中,而不是覆盖它)


我的python控制台上的概念验证:

>>> import pipes
>>> t = pipes.Template()
>>> t.open("/tmp/file1", "w")
<_io.TextIOWrapper name='/tmp/file1' mode='w' encoding='UTF-8'>
>>> pipefile = t.open("/tmp/file1", "r")
>>> i = 0
>>> while not pipefile.read():
...     i += 1
... 
然后我回到我的python选项卡,而
while
循环不再执行,因此我可以检查
I
在我离开时发生了什么

>>> print(i)
1704312

一般来说,有两种主要方法(据我所知)。第一种方法是让脚本检查一些可以从外部修改的条件(如某些文件/套接字的存在或内容)。或者如@Green Coep Guy所述,使用管道,这是进程间通信的一种形式

第二种方法是使用python运行的每个操作系统中存在的。当用户按下Ctrl+C时,终端会向前台的进程发送一个特定的信号。但是您可以通过编程方式(即从另一个脚本)发送相同(或另一个)的信号

阅读你的另一个问题的答案,我想说,解决这个问题缺少的是一种向已经运行的进程发送适当信号的方法。基本上这是可以做到的。请注意,尽管函数名为“kill”,但它可以发送任何信号(不仅仅是
SIGKILL

要使其工作,您需要具有正在运行的进程的进程id。了解这一点的一种常用方法是,让脚本在启动时将其进程id保存到存储在公共位置的文件中。要获取当前进程id,请执行以下操作

所以总结一下,我想说,实现你想要的目标的步骤是:

  • 修改当前脚本以将其进程id(可通过使用
    os.getpid()
    )存储到公共位置的文件中,例如
    /tmp/myscript.pid
    。请注意,如果希望脚本可移植,则需要以在类似Windows的非unix操作系统中工作的方式解决此问题
  • 选择一个信号(通常是
    SIGINT
    SIGSTOP
    SIGTERM
    )并修改脚本,以使用解决脚本正常终止的
    signal.signal()
    注册自定义处理程序
  • 创建另一个(请注意,它可能是与某些命令行参数相同的脚本)脚本,从已知文件(aka
    /tmp/myscript.pid
    )读取进程id,并使用
    os.kill()
    将所选信号发送到该进程
  • 请注意,使用信号实现t
    >>> print(i)
    1704312