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