【问题标题】:subprocess.Popen.stdout - reading stdout in real-time (again)subprocess.Popen.stdout - 实时读取标准输出(再次)
【发布时间】:2011-03-09 14:02:52
【问题描述】:

同样的问题。
原因是 - 阅读以下内容后我仍然无法使其工作:

我的情况是我有一个用 C 编写的控制台应用程序,让我们以循环中的代码为例:

tmp = 0.0;   
printf("\ninput>>"); 
scanf_s("%f",&tmp); 
printf ("\ninput was: %f",tmp); 

它不断地读取一些输入并写入一些输出。

我与之交互的python代码如下:

p=subprocess.Popen([path],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
p.stdin.write('12345\n')
for line in p.stdout: 
    print(">>> " + str(line.rstrip())) 
    p.stdout.flush() 

到目前为止,每当我阅读表单 p.stdout 时,它总是等到进程终止,然后输出一个空字符串。我已经尝试了很多东西 - 但仍然是相同的结果。

我尝试了 Python 2.6 和 3.1,但版本无关紧要 - 我只需要让它在某个地方工作。

【问题讨论】:

标签: python subprocess stdout popen


【解决方案1】:

由于默认缓冲在两个方向上进行,因此尝试向子进程写入和读取管道是很棘手的。当一个或另一个进程(父进程或子进程)正在从空缓冲区读取、写入完整缓冲区或在系统库刷新数据之前对等待数据的缓冲区进行阻塞读取时,很容易出现死锁。

对于更少量的数据,Popen.communicate() 方法可能就足够了。但是,对于超出其缓冲的数据,您可能会遇到停滞的进程(类似于您已经看到的?)

您可能想了解有关使用fcntl 模块并使文件描述符中的一个或另一个(或两者)非阻塞的详细信息。当然,在这种情况下,您必须将所有对这些文件描述符的读取和/或写入包装在适当的异常处理中,以处理“EWOULDBLOCK”事件。 (我不记得为这些引发的确切 Python 异常)。

一种完全不同的方法是让你的父进程使用select 模块和os.fork() ...,并让子进程在直接处理任何文件dup() 之后使用execve() 目标程序。 (基本上,您将重新实现 Popen() 的一部分,但使用不同的父文件描述符 (PIPE) 处理。

顺便说一句,.communicate,至少在 Python 的 2.5 和 2.6 标准库中,只能处理大约 64K 的远程数据(在 Linux 和 FreeBSD 上)。这个数字可能会因各种因素而异(可能包括用于编译 Python 解释器的构建选项,或者链接到它的 libc 版本)。它不仅受限于可用内存(尽管 J.F. Sebastian 的断言相反),而是受限于更小的值。

【讨论】:

  • 您能提供一个示例实现吗?我只是用Queue 模块玩了一下,但它根本不起作用。
  • +1 提到缓冲是罪魁祸首,另请参阅 Why not just use a pipe (popen())? 来自 pexpect 文档。但答案过于悲观,即.communicate() 适用于任何可以放入内存的数据(远大于管道缓冲区大小)。 fcntl(可能)也不能在 Windows 上工作,也没有必要。
  • .communicate() does work。早期版本可能有一个现在已修复的错误。
【解决方案2】:

将从管道中读取数据推送到一个单独的线程中,该线程会在输出块可用时发出信号:

How can I read all availably data from subprocess.Popen.stdout (non blocking)?

【讨论】:

    【解决方案3】:

    bufsize=256 参数防止 12345\n 在小于 256 字节的块中发送到子进程,就像在 p.stdin.write() 之后省略 bufsize 或插入 p.stdin.flush() 时一样。默认行为是行缓冲。

    在任何一种情况下,您至少应该在阻塞之前看到一个空行,正如您的示例中的第一个 printf(\n...) 发出的那样。

    【讨论】:

      【解决方案4】:

      您的特定示例不需要“实时”交互。以下作品:

      from subprocess import Popen, PIPE
      
      p = Popen(["./a.out"], stdin=PIPE, stdout=PIPE)
      output = p.communicate(b"12345")[0] # send input/read all output
      print output,
      

      a.out 是您的示例 C 程序。

      一般来说,对于与子进程的基于对话框的交互,您可以使用pexpect 模块(或其在 Windows 上的类似物):

      import pexpect
      
      child = pexpect.spawn("./a.out")
      child.expect("input>>")
      child.sendline("12345.67890") # send a number
      child.expect(r"\d+\.\d+") # expect the number at the end
      print float(child.after) # assert that we can parse it
      child.close()
      

      【讨论】:

        【解决方案5】:

        我遇到了同样的问题,“proc.communicate()”没有解决它,因为它等待进程终止。

        这就是在 Windows 上使用 Python 3.5.1 对我有用的方法:

        import subprocess as sp
        myProcess = sp.Popen( cmd, creationflags=sp.CREATE_NEW_PROCESS_GROUP,stdout=sp.PIPE,stderr=sp.STDOUT)
        while i<40:
                i+=1
                time.sleep(.5)
                out = myProcess.stdout.readline().decode("utf-8").rstrip()
        

        我猜想creationflags 和其他参数不是强制性的(但我​​没有时间测试),所以这将是最小的语法:

        myProcess = sp.Popen( cmd, stdout=sp.PIPE)
        for i in range(40)
                    time.sleep(.5)
                    out = myProcess.stdout.readline()
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-03-07
          • 2014-07-08
          • 1970-01-01
          • 2022-01-04
          • 1970-01-01
          • 2013-02-24
          • 1970-01-01
          相关资源
          最近更新 更多