Python,子进程:从子进程读取输出

13

我有如下脚本:

#!/usr/bin/python

while True:
    x = raw_input()
    print x[::-1]

我是从ipython中调用它的:

In [5]: p = Popen('./script.py', stdin=PIPE)

In [6]: p.stdin.write('abc\n')
cba

它能正常运行。

但是,当我执行以下操作时:

In [7]: p = Popen('./script.py', stdin=PIPE, stdout=PIPE)

In [8]: p.stdin.write('abc\n')

In [9]: p.stdout.read()

解释器挂起了,我做错了什么?我想能够多次从另一个进程中读取和写入数据,以将某些任务传递给该进程。我需要做什么不同的事情?

编辑1

如果我使用communicate,我会得到这个:

In [7]: p = Popen('./script.py', stdin=PIPE, stdout=PIPE)

In [8]: p.communicate('abc\n')
Traceback (most recent call last):
  File "./script.py", line 4, in <module>
    x = raw_input()
EOFError: EOF when reading a line
Out[8]: ('cba\n', None)

编辑2

我尝试了清空缓存:

#!/usr/bin/python

import sys

while True:
        x = raw_input()
        print x[::-1]
        sys.stdout.flush()

还有这里:

In [5]: from subprocess import PIPE, Popen

In [6]: p = Popen('./script.py', stdin=PIPE, stdout=PIPE)

In [7]: p.stdin.write('abc')

In [8]: p.stdin.flush()

In [9]: p.stdout.read()

但它再次卡住了。

6个回答

15

我认为这里存在两个问题:

1)您的父脚本调用了 p.stdout.read(),它会读取所有数据直到文件结尾。然而,您的子脚本在一个无限循环中运行,因此不会出现文件结尾。您可能想使用 p.stdout.readline() 吗?

2)在交互模式下,大多数程序仅缓冲一行。但是,当从另一个程序运行时,它们会缓冲更多内容。缓冲在许多情况下可以提高效率,但在两个程序需要进行交互通信时会导致问题。

p.stdin.write('abc\n')之后添加:

p.stdin.flush()
在你的子进程脚本中,在 print x[::-1] 之后,在循环内添加以下内容:
sys.stdout.flush()

(并在顶部添加 import sys)


@gruszczy:感谢更新。我已经更新了我的答案以解决另一个问题。 - Daniel Stutzbach
2
sys.stdout.flush() 添加到脚本中,并使用 p.stdout.readline 最终解决了问题。非常感谢您的帮助。 - gruszczy
感谢Daniel Statzbash让我的生活变得更轻松。(即使写了“\n”后仍然刷新) - Benji Mizrahi

3

如果您想将多行文本传递给 script.py,则需要同时进行读写:

#!/usr/bin/env python
import sys
from subprocess import PIPE, Popen
from threading  import Thread

def print_output(out, ntrim=80):
    for line in out:
        print len(line)
        if len(line) > ntrim: # truncate long output
            line = line[:ntrim-2]+'..'
        print line.rstrip() 


if __name__=="__main__":
    p = Popen(['python', 'script.py'], stdin=PIPE, stdout=PIPE)
    Thread(target=print_output, args=(p.stdout,)).start()
    for s in ['abc', 'def', 'ab'*10**7, 'ghi']:
        print >>p.stdin, s
    p.stdin.close()
    sys.exit(p.wait()) #NOTE: read http://docs.python.org/library/subprocess.html#subprocess.Popen.wait

输出:

4
cba
4
fed
20000001
bababababababababababababababababababababababababababababababababababababababa..
4
ihg

script.py 文件内容:

#!/usr/bin/env python
"""Print reverse lines."""
while True:
    try: x = raw_input()
    except EOFError:
        break # no more input
    else:
        print x[::-1]

或者

#!/usr/bin/env python
"""Print reverse lines."""
import sys

for line in sys.stdin:
    print line.rstrip()[::-1]

或者

#!/usr/bin/env python
"""Print reverse lines."""
import fileinput

for line in fileinput.input(): # accept files specified as command line arguments
    print line.rstrip()[::-1]

“print >>p.stdin, s”语法是什么意思?我不熟悉“>>”。 - Civilian
@Civilian: ["print chevron"] (http://docs.python.org/reference/simple_stmts.html#grammar-token-print_stmt) 类似于 [print(s, file=p.stdin)] (http://docs.python.org/library/functions.html#print),需要先使用 from __future__ import print_function - jfs

3

子进程方法 check_output 可以用于此目的:

output = subprocess.check_output('./script.py')

输出将是进程的 stdout。如果你也需要 stderr:

output = subprocess.check_output('./script.py', stderr=subprocess.STDOUT)

由于避免了直接处理管道,因此可能会避免你的问题。


1

你可能被Python的输出缓冲所困扰。这是python --help对此的解释。

-u     : unbuffered binary stdout and stderr; also PYTHONUNBUFFERED=x
         see man page for details on internal buffering relating to '-u'

1
我能不能以某种方式强制Python在代码中不刷新输出?为什么它被缓冲,当它被推送到管道中时,而不是在简单地打印到屏幕上时?我能否通过编程方式刷新缓冲区? - gruszczy
输出到管道的内容会被缓存以提高性能。(逐行刷新非常低效,但当程序大部分时间都在等待用户时,你不会注意到)在向管道刷新时,缓存可以显著提高性能,无论是提高完成时间还是减少 CPU 负载,这取决于操作有多少 I/O 绑定。 - ssokolow
关于程序化刷新缓冲区,您可以尝试在子进程中使用sys.stdout.flush(),但我从未使用过它,并且以这种方式使用它并不是真正的良好架构。(它更适用于与os.fsync一起作为“同步到磁盘”过程的一部分)最好让父进程通过-uPYTHONUNBUFFERED环境变量根据需要禁用缓冲。 - ssokolow

1

当你写完 p.stdin 后,关闭它:p.stdin.close()


-3

使用communicate()代替.stdout.read()

示例:

from subprocess import Popen, PIPE
p = Popen('./script.py', stdin=PIPE, stdout=PIPE, stderr=PIPE)
input = 'abc\n'
stdout, stderr = p.communicate(input)

这个建议来自于Popen对象部分在子进程文档中:

警告:使用communicate()而不是.stdin.write、.stdout.read或.stderr.read,以避免由于任何其他操作系统管道缓冲区填满并阻塞子进程而导致死锁。


这对我不起作用。我已经在我的问题中进行了编辑。 - gruszczy
5
他希望与子进程进行多次通信。 communicate不能做到这一点。 - Daniel Stutzbach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接