我想从this帖子中描述的同一个帖子中的子流程中读取stdout
和stderr
。虽然在Python2.7中运行代码按预期工作,但Python3.3中的select()
调用似乎不应该如此。
看看 - 这是一个脚本,可以在stdout
和stderr
上打印两行,然后等待,然后重复几次:
import time, sys
for i in range(5):
sys.stdout.write("std: %d\n" % i)
sys.stdout.write("std: %d\n" % i)
sys.stderr.write("err: %d\n" % i)
sys.stderr.write("err: %d\n" % i)
time.sleep(2)
有问题的脚本将在子流程中启动上面的脚本,并按照发布的链接中所述读取stdout
和stderr
:
import subprocess
import select
p = subprocess.Popen(['/usr/bin/env', 'python', '-u', 'test-output.py'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
r = [p.stdout.fileno(), p.stderr.fileno()]
while p.poll() is None:
print("select")
ret = select.select(r, [], [])
for fd in ret[0]:
if fd == p.stdout.fileno():
print("readline std")
print("stdout: " + p.stdout.readline().decode().strip())
if fd == p.stderr.fileno():
print("readline err")
print("stderr: " + p.stderr.readline().decode().strip())
请注意,我使用-u
选项启动Python子进程,导致Python不缓冲stdout
和stderr
。此外,我在调用select()
和readline()
之前打印一些文本,以查看脚本阻止的位置。
这就是问题:在Python3中运行脚本,在每个周期后输出阻塞2秒,尽管事实上还有两行等待读取。如select()
每次调用前的文字所示,您可以看到select()
阻止了(readline()
)。
我的第一个想法是select()
仅在Python3上的刷新时恢复,而Python2它总是在有可用数据的情况下返回,但在这种情况下,每2秒只读取一行(事实并非如此!)
所以我的问题是:这是Python3-select()中的一个错误?我是否误解了select()
的行为?有没有办法解决这个问题而不必为每个管道启动一个线程?
运行Python3时的输出:
select
readline std
stdout: std: 0
readline err
stderr: err: 0
select <--- here the script blocks for 2 seconds
readline std
stdout: std: 0
select
readline std
stdout: std: 1
readline err
stderr: err: 0
select <--- here the script should block (but doesn't)
readline err
stderr: err: 1
select <--- here the script blocks for 2 seconds
readline std
stdout: std: 1
readline err
stderr: err: 1
select <--- here the script should block (but doesn't)
readline std
stdout: std: 2
readline err
stderr: err: 2
select
.
.
编辑:请注意,它不会影响子进程是否为Python脚本。以下C ++程序具有相同的效果:
int main() {
for (int i = 0; i < 4; ++i) {
std::cout << "out: " << i << std::endl;
std::cout << "out: " << i << std::endl;
std::cerr << "err: " << i << std::endl;
std::cerr << "err: " << i << std::endl;
fflush(stdout);
fflush(stderr);
usleep(2000000);
}}
答案 0 :(得分:1)
似乎原因是subprocess.PIPE
中的缓冲,并且第一个readline()
调用读取所有可用数据(即两行)并返回第一个。
之后,管道中没有未读数据,因此select()
不会立即返回。你可以通过加倍readline调用来检查这个:
print("stdout: " + p.stdout.readline().decode().strip())
print("stdout: " + p.stdout.readline().decode().strip())
并确保第二次readline()
调用不会阻止。
一种解决方案是使用bufsize=0
禁用缓冲:
p = subprocess.Popen(['/usr/bin/env', 'python', '-u', 'test-output.py'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
另一种可能的解决方案是执行非阻塞readline()
或向管道文件对象询问其读缓冲区大小,但我不知道是否可能。
您还可以直接从p.stdout.fileno()
阅读,以实现非阻止readline()
。
更新: Python2与Python3
Python3与Python2的不同之处可能在于新的I / O模块(PEP 31136)。请参阅此说明:
BufferedIOBase方法签名大多与RawIOBase签名相同(例外:write()返回None,read()的参数是可选的),但可能有不同的语义。特别是,BufferedIOBase实现可能会读取比请求更多的数据或使用缓冲区延迟写入数据。