使用stdin和stdout在2个进程之间进行通信

Ofe*_*man 3 python subprocess

我想编写一个执行外部脚本(B)的简单脚本(A)

  • A应该通过写入B的stdin并读取其stdout与B进行通信
  • B应该阅读其标准输入并打印

所有这些都应该在不关闭流的情况下完成

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())
Run Code Online (Sandbox Code Playgroud)

py

import sys

for line in sys.stdin:
    print(line)
Run Code Online (Sandbox Code Playgroud)

输出应为:

>>> b'hello'
>>> b'hello'
>>> b'hello'
Run Code Online (Sandbox Code Playgroud)

问题是A只是在等待

print(process.stdout.read())
Run Code Online (Sandbox Code Playgroud)

如果我通过添加close()来修改A:

for _ in range(3):
    process.stdin.write(b'hello')
    process.stdin.close()
    print(process.stdout.read())
Run Code Online (Sandbox Code Playgroud)

我得到:

>>> 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
Run Code Online (Sandbox Code Playgroud)

Vyk*_*tor 5

使用 communicate()

Python已经communicate()实现了方法(转到A.pyB.py就可以了)。但是,如果您需要更复杂的数据,则它仅适用于简单的通信(您知道要先发送哪些数据):

send data to process B
read stdout
if stdout ...
    do something bases on stdout
    write to stdin
Run Code Online (Sandbox Code Playgroud)

您必须在这里实现自己的communicate()原始实现。


一步步

我已经一步一步地测试和调试了,这是发生了什么:

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

所以添加后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
Run Code Online (Sandbox Code Playgroud)

那有什么用呢?

# 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()
Run Code Online (Sandbox Code Playgroud)

讲解

您已将缓冲设置为io.DEFAULT_BUFFER_SIZE(通常为4090B)。从文档

在创建stdin / stdout / stderr管道文件对象时,bufsize将作为io.open()函数的相应参数提供:0表示未缓冲(读和写是一个系统调用,并且可以返回short),1表示行缓冲,任何其他正值表示使用大约该大小的缓冲区。负bufsize(默认值)表示将使用io.DEFAULT_BUFFER_SIZE的系统默认值。

因此,起初A不会刷新,因为它尚未填满缓冲区,因此B正在等待。它不可能简单地 process.stdin.flush()在Windows下,所以你必须使用bufsize=0

由于的缘故,写os.linesep\r\n也很重要readline()

注意:我相信它也应该与bufsize=1(行缓冲)一起使用,但是没有。我不知道为什么。

然后,B当它不会刷新时,也会发生同样的事情,sys.stdout这让我感到惊讶,它B:sys.stdout没有设置为无缓冲的,因为:

创建stdin / stdout / stderr管道文件对象时,bufsize将作为io.open()函数的相应参数提供。

无论如何,你必须调用sys.stdout.flush()B

close()之所以与之合作是因为它具有强制性flush()


Gimme代码

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())
Run Code Online (Sandbox Code Playgroud)

B.py

import sys

for line in sys.stdin:
    print('Send back', line.strip())
    sys.stdout.flush()
Run Code Online (Sandbox Code Playgroud)