为什么无缓冲的 stderr 会比有缓冲的 stdout 输出的还晚?

2019-01-15 13:49:30 +08:00
 OhYee
import sys
sys.stdout.write("stdout1 ")
sys.stderr.write("stderr1 ")
sys.stdout.write("stdout2 ")
sys.stderr.write("stderr2 ")

print()
string = ""
for i in range(10):
    string = string + str(i) + "\n"
print(string)

在使用 python test.py 执行时,应该是有开启缓冲区的,不加上后面的 print 部分,输出顺序是 stderr1 stderr2 stdout1 stdout2

但是为什么加了后面的 print 部分输出变成了

stdout1 stdout2
0
1
2
3
4
5
6
7
8
9

stderr1 stderr2

stderr 不是无缓冲的么,不应该直接就输出出来在最前面么?

2354 次点击
所在节点    问与答
22 条回复
kkk330
2019-01-15 14:16:19 +08:00
https://docs.python.org/3/library/sys.html#sys.stderr

里面有提到
When interactive, stdout and stderr streams are line-buffered. Otherwise, they are block-buffered like regular text files. You can override this value with the -u command-line option.

```shell

python3 test.py

python3 test.py &> buffer.log

python3 -u test.py

```

顺便, 也可以看看 python2 的表现, 我这边测试了下是有差异的
kkk330
2019-01-15 14:27:54 +08:00
再补充下, 行缓冲是关键, 如果你在 sys.stderr.write 里加上\n, 那么你的输出就直接是符合预期的
OhYee
2019-01-15 14:29:00 +08:00
@kkk330

我确认了下我是有开缓冲区的,没有加-u,PYTHONUNBUFFERED 也是空的

如果按照缓冲区的理解,输出结果应该是这样才对

```python
stderr1 stderr2 stdout1 stdout2
0
1
2
3
4
5
6
7
8
9

```

stderr 明明没有用缓冲区直接输出,竟然反而比走缓冲区的 stdout 还慢。
OhYee
2019-01-15 14:29:58 +08:00
@kkk330
行缓冲应该不会影响结果吧,就算是 print 的换行影响了结果,stderr 也应该在前面啊
OhYee
2019-01-15 14:35:58 +08:00
把 print 换成 sys.stdout.write 符合预期了,所以说 print 不是一般意义的 stdout 么
kkk330
2019-01-15 14:38:06 +08:00
是行缓冲的原因, 可以一步一步看
sys.stdout.write("stdout1 ") # stdout 里变成了"stdout1 "
sys.stderr.write("stderr1 ") # stderr 里变成了"stderr1 "
sys.stdout.write("stdout2 ") # stdout 里变成了"stdout1 stdout2 "
sys.stderr.write("stderr2 ") # stderr 里变成了"stderr1 stderr2"

print() # stdout 里变成了"stdout1 stdout2 \n" 因为是行缓冲, 发现了\n, 所以就 flush 到 tty 了

.....

# 代码最后这里执行完了, stderr 仍然没有\n, 因为程序结束了, 还是会被 flush 出来, 所以 stderr 在最后输出
kkk330
2019-01-15 14:39:23 +08:00
print 自带\n
也就是 sys.stdout.write("xxxxx\n")等效于 print("xxxxx")
OhYee
2019-01-15 14:40:54 +08:00
stderr 不是无缓冲的么?他不应该直接输出不用等\n 么
OhYee
2019-01-15 14:41:13 +08:00
@kkk330 stderr 不是无缓冲的么?他不应该直接输出不用等\n 么
kkk330
2019-01-15 14:45:56 +08:00
"When interactive, stdout and stderr streams are line-buffered."

你应该是直接在终端里执行的命令, 因为是 tty, 所以属于交互式的, 所以是行缓冲
kkk330
2019-01-15 14:46:33 +08:00
我前面还故意列了几个执行方式让你看看他们的表现的...
swulling
2019-01-15 14:47:30 +08:00
@OhYee stderr 在 Python3 是 line-buffered 的,在 Python2 是无 buffer 的
从 print()看,你这个是 Python3 吧,一楼说的很清楚了
OhYee
2019-01-15 14:51:05 +08:00
@kkk330
python test.py 运行的
不是直接终端运行的。

别的地方都和我理解的一样,只有混着 print 不符合理解
感觉 stderr 的无缓冲也不是简单的无缓冲

比如针对
'''python
string = ""
for i in range(10000):
string = string + str(i) + "\n"
'''

因为在不加-u 时,
sys.stderr.write(string)
sys.stdout.write(string)
可以正常输出(输出到 9999)

而加上-u,
sys.stderr.write(string)
sys.stdout.write(string)
两者都只能输出到 2638
OhYee
2019-01-15 14:53:26 +08:00
@swulling
python3 里不也是 stdout 是 line buffer,stderr 是无 buffer 么

所以按照期望不管后面的内容有没有换行, stderr1 应该一定在 stdout2 前面吧

按照我测试的情况来看,stderr 也是 line-buffer,不过网上貌似都是说 stderr 是没有 buffer 的。

另外 13 楼的输出被截断也是 buffer 的问题么?
OhYee
2019-01-15 15:04:45 +08:00
@kkk330

几种执行方式的结果来看

(因为被认为是外链,所以. 后面都加了空格)

python3 test. py 不符合预期,stderr 作为无 buffer 不应该在最后么?
python3 -u test. py 符合预期
python3 test. py >log. txt 符合预期
python3 test. py &>log. txt 符合预期



执行结果:
```
dev@u:~$ python3 test. py
stdout1 stdout2
0
1
2
3
4
5
6
7
8
9

stderr1 stderr2 dev@u:~$
dev@u:~$
dev@u:~$ python3 -u test. py
stdout1 stdout2
0
1
2
3
4
5
6
7
8
9

stderr1 stderr2 dev@u:~$
dev@u:~$
dev@u:~$ python3 test. py >log. txt
stderr1 stderr2 dev@u:~$
dev@u:~$ cat log. txt
stdout1 stdout2
0
1
2
3
4
5
6
7
8
9

dev@u:~$ python3 test. py &>log. txt
dev@u:~$ cat log. txt
stderr1 stderr2 stdout1 stdout2
0
1
2
3
4
5
6
7
8
9
```
kkk330
2019-01-15 15:09:26 +08:00
"你应该是直接在终端里执行的命令, 因为是 tty, 所以属于交互式的, 所以是行缓冲"

你在 15 楼贴的就是"在终端里执行的命令"
whileFalse
2019-01-15 15:12:16 +08:00
@kkk330 学习了 谢谢
OhYee
2019-01-15 15:19:19 +08:00
@kkk330

Interactive interpreter 不是指直接 python3 然后终端里写代码么
类似
$ python3
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

https://docs.python.org/3/tutorial/interpreter.html

这种应该是属于 script 运行吧

另外,看了下文档,block-buffered 要怎么翻译啊, 块缓冲还是禁用缓冲?

我没有在官方文档找到百度上常见的 stderr 是无缓冲类似的解释
swulling
2019-01-15 15:42:57 +08:00
@OhYee https://docs.python.org/3/library/sys.html#sys.stderr

When interactive, stdout and stderr streams are line-buffered. Otherwise, they are block-buffered like regular text files. You can override this value with the -u command-line option.

你不懂英文么?
OhYee
2019-01-15 15:52:08 +08:00
@kkk330 @swulling

OK, 明白了, python 里 stderr 是 line-buffer。
查到的资料有点混乱,而且 py2 的解释比较多所以搞乱了。
感谢两位大佬的解释。

另外关于-u 会截断输出内容的方面有什么解释么?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/527210

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX