Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/flash/4.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:在遍历stdout时从子进程捕获异常_Python_Subprocess_Stderr - Fatal编程技术网

Python:在遍历stdout时从子进程捕获异常

Python:在遍历stdout时从子进程捕获异常,python,subprocess,stderr,Python,Subprocess,Stderr,我正在尝试从子进程获取异常。如果我使用.communicate,我就可以得到它,但我希望避免使用它,因为我在子流程中进行流式输出,不想等到整个子流程完成。还假设整个子流程可能需要很长时间。我想知道如何捕获从子进程流式传输标准输出时引发的异常 考虑下面的例子,所以我希望版本1可以工作,版本2可以工作,但我不希望这样 在main.py中 import subprocess class ExtProcess(): def __init__(self, *args): sel

我正在尝试从子进程获取异常。如果我使用
.communicate
,我就可以得到它,但我希望避免使用它,因为我在子流程中进行流式输出,不想等到整个子流程完成。还假设整个子流程可能需要很长时间。我想知道如何捕获从子进程流式传输标准输出时引发的异常

考虑下面的例子,所以我希望版本1可以工作,版本2可以工作,但我不希望这样

在main.py中

import subprocess


class ExtProcess():
    def __init__(self, *args):
        self.proc = subprocess.Popen(['python', *args], stdout=subprocess.PIPE)

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            line = self.proc.stdout.readline()
            if self.proc.returncode:
                raise Exception("error here")
            if not line:
                raise StopIteration
            return line


def run():
    ## version #1
    reader = ExtProcess("sample_extproc.py")
    for d in reader:
        print(f"got: {d}")

    ## version #2
    # proc = subprocess.Popen(['python', "sample_extproc.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # output, error = proc.communicate()
    # print("got:", output)
    # if proc.returncode:
    #     raise Exception(error)

def main():
    try:
        print("start...")
        run()
        print("complete...")
    except Exception as e:
        print(f"Package midstream error here: {str(e)}")
    finally:
        print("clean up...")


if __name__ == "__main__":
    main()
在示例_extproc.py中

for x in range(10):
    print(x)
    if x == 3:
        raise RuntimeError("catch me")
我想从版本1中获得如下输出:

开始。。。
获取:b'0\r\n'
获取:b'1\r\n'
获取:b'2\r\n'
获取:b'3\r\n'
此处的包中游错误:b'Traceback(最近一次调用):\r\n文件“sample\u extproc.py”,第4行,在\r\n raise RuntimeError(“catch me”)\r\n运行时错误:catch me\r\n
清理

基本上,它从子流程迭代标准输出,然后在异常发生时打印异常,然后继续执行清理。

以下是我的问题的答案,这是基于@CharlesDuffy的评论:

简言之,确保在
ExtProcess
类中有
stderr=subprocess.PIPE
,然后答案在版本3中,在迭代stdout之后,我们使用
.wait()
returncode
检查是否有错误,如果有,则引发异常,从
stderr.read()抓取错误
在父级/主级中捕获

import subprocess

class ExtProcess():
    def __init__(self, *args):
        self.proc = subprocess.Popen(['python', *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            line = self.proc.stdout.readline()
            if not line:
                raise StopIteration
            return line


def run():
    ## version #1
    # reader = ExtProcess("sample_extproc.py")
    # for d in reader:
    #     print(f"got: {d}")

    ## version #2
    # proc = subprocess.Popen(['python', "sample_extproc.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # output, error = proc.communicate()
    # print("got:", output)
    # if proc.returncode:
    #     raise Exception(error)

    ## version #3
    reader = ExtProcess("sample_extproc.py")
    for d in reader:
        print(f"got: {d}")
    reader.proc.wait()
    if reader.proc.returncode:
       raise Exception(reader.proc.stderr.read())

def main():
    try:
        print("start...")
        run()
        print("complete...")
    except Exception as e:
        print(f"Package midstream error here: {str(e)}")
    finally:
        print("clean up...")


if __name__ == "__main__":
    main()

程序在退出之前不会返回退出状态。子进程在抛出异常时是否退出?…因此,当您仍在读取数据时,尝试检查
returncode
几乎没有意义。当然,进程可以在退出之前关闭其标准输出,或者在退出时几十个字节仍在FIFO中,但是超过毫秒是非常罕见的,即使如此,父进程也不会知道退出状态,直到调用
wait()
syscall来检索它(当从僵尸条目中获取已退出进程的PID时,它会在进程表中保留退出调用和其父进程读取该状态之间的时间)。我确实想问“你更大的目标是什么?”在这里可能是合适的。我喜欢“回答被问到的问题”,但我忍不住觉得有另一种方法来解决这个问题(取决于它是什么)…如果你的子进程一看到异常就退出,那么该异常就不会“在中间”,但将位于流的末尾,因此您可以直接从proc.self.stdout:循环退出到
p.wait()中的
for行
,并在
等待
返回时立即检查
p.returncode
。@CharlesDuffy很抱歉刚刚理解了您提到的内容。因此,是的,如果在我循环通过stdout之后,让p.wait,然后检查p.returncode,它实际上是有效的。因此,阅读此代码时唯一让我担心的是,您的程序是否试图编写比预期更多的内容在关闭stdout之前将其放入stderr的管道中,这种写入可能会被阻塞,因为没有任何东西在读取它(stderr不仅仅用于错误——它也是日志等“诊断内容”所属的地方);因此,如果程序没有完成对stdout的写入(或没有尝试关闭stdout),则可能会导致死锁在对stderr的写入完成之前。@CharlesDuffy-Hmm不确定我是否完全理解,但是我可以使用的东西是否有超时?我假设每当我到达p.wait(),stdout已经到达StopIteration,所以我不应该在p.wait()上等待太久潜在的问题是子进程死锁。请尝试使用一个子进程,该子进程在执行过程中会向stderr写入几kb的数据——除非父进程中的某些内容当时正在主动从stderr读取数据,否则它写入该数据的尝试将挂起;如果在stdout退出后才从stderr读取数据,则t意味着您无法安全地运行将超过FIFO缓冲区值的数据写入stderr的子进程,而它们仍然打开stdout。另一种方法是使用;中的一些答案进入其中。只需将stderr中的数据读取到父进程中,并存储这些数据以供以后参考就足够了……使用
选择器的意义在于
同时从stdout和stderr读取,或者线程在后台从
proc.stderr
读取,等等都是为了确保在子进程写入stderr时使用它,这样子进程就不能挂起,试图等待连接到其stderr的完整FIFO缓冲区有空间写入更多数据en(而父级不努力读取,因此根本不清空FIFO)。
import subprocess

class ExtProcess():
    def __init__(self, *args):
        self.proc = subprocess.Popen(['python', *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            line = self.proc.stdout.readline()
            if not line:
                raise StopIteration
            return line


def run():
    ## version #1
    # reader = ExtProcess("sample_extproc.py")
    # for d in reader:
    #     print(f"got: {d}")

    ## version #2
    # proc = subprocess.Popen(['python', "sample_extproc.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # output, error = proc.communicate()
    # print("got:", output)
    # if proc.returncode:
    #     raise Exception(error)

    ## version #3
    reader = ExtProcess("sample_extproc.py")
    for d in reader:
        print(f"got: {d}")
    reader.proc.wait()
    if reader.proc.returncode:
       raise Exception(reader.proc.stderr.read())

def main():
    try:
        print("start...")
        run()
        print("complete...")
    except Exception as e:
        print(f"Package midstream error here: {str(e)}")
    finally:
        print("clean up...")


if __name__ == "__main__":
    main()