【问题标题】:How to make a python script stopable from another script?如何使 python 脚本可以从另一个脚本停止?
【发布时间】:2019-11-21 14:04:08
【问题描述】:

TL;DR:如果你有一个程序应该运行一段不确定的时间,当用户决定时间到时你如何编写代码来停止它? (无需 KeyboardInterrupt 或终止任务)

--

我最近发布了这个问题:How to make my code stopable? (Not killing/interrupting) 答案确实解决了我的问题,但从终止/中断的角度来看,这并不是我真正想要的。 (虽然,我的问题没有说清楚) 所以,我正在改写它。

出于示例目的,我创建了一个通用脚本。所以我有这个类,它从通用 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()

脚本应该永远运行或直到我命令它停止。我知道我可以KeyboardInterrupt (Ctrl+C) 或突然终止任务。 这不是我想要的。我想要一种“软”的方式来告诉脚本该停止了,不仅因为中断是不可预测的,而且它也是一种严厉的停止方式.

如果该脚本在 docker 容器上运行(例如),您将无法Ctrl+C,除非您碰巧在 docker 内的终端/bash 中。

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

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

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

有没有办法做到这一点?

如果您有一个程序应该运行一段不确定的时间,那么当用户决定是时候停止它时,您如何编写代码来阻止它?

【问题讨论】:

  • 您可以执行类似的操作,例如让collect_data() 方法每隔一段时间检查一个特定文件是否存在,如果该文件存在,那么它将退出。所以你需要做的就是创建那个文件来停止脚本
  • 相关:stackoverflow.com/questions/6920858/…。您可以使用该库让两个不同的进程进行通信。一个可以发送停止信号,另一个可以在收到停止信号时进行监听并采取相应的行动

标签: python


【解决方案1】:

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

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

阅读您其他问题的答案我想说,解决这个问题所缺少的是一种向您已经运行的进程发送适当信号的方法。基本上这可以通过by using the os.kill() function 完成。请注意,虽然该函数被称为“kill”,但它可以发送任何信号(不仅是SIGKILL)。

为了使它工作,您需要拥有正在运行的进程的进程 ID。了解这一点的常用方法是让您的脚本在启动到存储在公共位置的文件时保存其进程 ID。要获取当前进程 ID,请 can use the os.getpid() function

总结一下,我想说实现您想要的步骤是:

  1. 修改当前脚本以将其进程 ID(可通过使用 os.getpid() 获得)存储到公共位置的文件中,例如 /tmp/myscript.pid。请注意,如果您希望您的脚本是可移植的,您将需要以一种适用于非 unix 操作系统(如 Windows)的方式来解决这个问题。
  2. 选择一个信号(通常是 SIGINTSIGSTOPSIGTERM)并修改您的脚本以使用 signal.signal() 注册一个自定义处理程序,以解决脚本的正常终止问题。
  3. 创建另一个(注意它可能是带有一些命令行参数的同一个脚本)从已知文件(又名/tmp/myscript.pid)中读取进程ID并将选择的信号发送给该脚本的脚本使用os.kill()处理。

请注意,使用信号而不是外部方式(文件、管道等)来实现此目的的一个优点是用户仍然可以按 Ctrl+C(如果您选择了SIGINT)并且会产生相同的效果行为就像“停止脚本”一样。

【讨论】:

    【解决方案2】:

    您真正需要的是任何将信号从一个程序发送到另一个独立程序的方法。一种方法是使用进程间管道。 Python has a module for this(诚然,这似乎需要符合 POSIX 标准的外壳,但大多数主要操作系统都应该提供该外壳)。

    您必须事先在运行程序(例如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
    ... 
    

    此时我转到另一个终端选项卡并执行

    $ echo "Stop" >> /tmp/file1
    

    然后我回到我的 python 选项卡,while 循环不再执行,所以我可以检查在我离开时 i 发生了什么。

    >>> print(i)
    1704312
    

    【讨论】:

      猜你喜欢
      • 2011-03-04
      • 1970-01-01
      • 2020-08-28
      • 1970-01-01
      • 2015-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-14
      相关资源
      最近更新 更多