通过stdin和stdout进行两个进程通信

3 投票
1 回答
2960 浏览
提问于 2025-04-28 12:55

我想写一个简单的脚本(A),它可以执行另一个外部脚本(B)。

  • A需要通过写入B的标准输入(stdin)来和B进行沟通,同时也要从B的标准输出(stdout)读取信息。
  • B则需要读取它的标准输入,并将内容打印出来。

这一切都要在不关闭流的情况下完成。

A.py

import subprocess
process = subprocess.Popen(['python', 'B.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for _ in range(3):
    process.stdin.write(b'hello')
    print(process.stdout.read())

B.py

import sys

for line in sys.stdin:
    print(line)

输出应该是:

>>> b'hello'
>>> b'hello'
>>> b'hello'

问题是A一直在等待。

print(process.stdout.read())

如果我在A中添加close(),

for _ in range(3):
    process.stdin.write(b'hello')
    process.stdin.close()
    print(process.stdout.read())

我得到的结果是:

>>> b'hello\n'
>>> Traceback (most recent call last):
>>>   File "A.py", line 7, in <module>
>>>     process.stdin.write(b'hello')
>>> ValueError: write to closed file
暂无标签

1 个回答

7

使用 communicate()

Python已经实现了communicate()这个方法(它适用于A.pyB.py也没问题)。不过这个方法只适合简单的通信(你事先知道要发送什么数据),如果你需要更复杂的通信,比如:

send data to process B
read stdout
if stdout ...
    do something bases on stdout
    write to stdin

你就得自己实现一个communicate(),原始实现可以在这里找到。


逐步解析

我一步步测试和调试了这个过程,下面是发生的事情:

# For Popen(bufsize!=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline() # Hangs

所以在添加了bufsize=0(无缓冲)之后

# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n') # Without \r\n B still hangs
B: line = sys.stdin.readline()
B: print('Send back', line.strip()) # Without strip it prints empty line
A: process.stdout.readline() # Hangs

那么,什么是有效的呢?

# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline()
B: print('Send back', line.strip())
B: sys.stdout.flush()
A: process.stdout.readline()

解释一下

你设置了缓冲区io.DEFAULT_BUFFER_SIZE(通常是4090字节)。根据文档

bufsize会作为相应的参数传递给io.open()函数,当创建stdin/stdout/stderr管道文件对象时:0表示无缓冲(读写是一次系统调用,可以返回短数据),1表示行缓冲,其他正值表示使用大约那个大小的缓冲区。负的bufsize(默认值)表示使用系统默认的io.DEFAULT_BUFFER_SIZE。

所以一开始A不会刷新,因为它的缓冲区还没填满,因此B在等待。 在Windows下,不能简单地使用process.stdin.flush(),所以你必须使用bufsize=0

另外,写入os.linesep\r\n是很重要的,因为这与readline()有关。

注意:我认为使用bufsize=1(行缓冲)也应该有效,但实际上没有。我不知道为什么。

然后在B中也会出现同样的情况,它不会刷新sys.stdout,这让我感到惊讶的是,B:sys.stdout并没有设置为无缓冲,因为:

bufsize会作为相应的参数传递给io.open()函数,当创建stdin/stdout/stderr管道文件对象时。

无论如何,你需要在B中调用sys.stdout.flush()

使用close()是有效的,因为它会强制flush()


给我代码

A.py

import subprocess
import sys

process = subprocess.Popen([sys.executable, r'B.py'], stdin=subprocess.PIPE, 
                            stdout=subprocess.PIPE, bufsize=0)
for _ in range(3):
    process.stdin.write(b'hello\r\n')
    print(process.stdout.readline())

B.py

import sys

for line in sys.stdin:
    print('Send back', line.strip())
    sys.stdout.flush()

撰写回答