首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
宝塔
V2EX  ›  问与答

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

  •  
  •   OhYee · 304 天前 · 1021 次点击
    这是一个创建于 304 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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 不是无缓冲的么,不应该直接就输出出来在最前面么?

        1
    kkk330   304 天前   ♥ 1
    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 的表现, 我这边测试了下是有差异的
        2
    kkk330   304 天前   ♥ 1
    再补充下, 行缓冲是关键, 如果你在 sys.stderr.write 里加上\n, 那么你的输出就直接是符合预期的
        3
    OhYee   304 天前
    @kkk330

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

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

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

    ```

    stderr 明明没有用缓冲区直接输出,竟然反而比走缓冲区的 stdout 还慢。
        4
    OhYee   304 天前
    @kkk330
    行缓冲应该不会影响结果吧,就算是 print 的换行影响了结果,stderr 也应该在前面啊
        5
    OhYee   304 天前
    把 print 换成 sys.stdout.write 符合预期了,所以说 print 不是一般意义的 stdout 么
        6
    kkk330   304 天前   ♥ 1
    是行缓冲的原因, 可以一步一步看
    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 在最后输出
        7
    kkk330   304 天前
    print 自带\n
    也就是 sys.stdout.write("xxxxx\n")等效于 print("xxxxx")
        8
    OhYee   304 天前
    stderr 不是无缓冲的么?他不应该直接输出不用等\n 么
        9
    OhYee   304 天前
    @kkk330 stderr 不是无缓冲的么?他不应该直接输出不用等\n 么
        10
    kkk330   304 天前   ♥ 1
    "When interactive, stdout and stderr streams are line-buffered."

    你应该是直接在终端里执行的命令, 因为是 tty, 所以属于交互式的, 所以是行缓冲
        11
    kkk330   304 天前
    我前面还故意列了几个执行方式让你看看他们的表现的...
        12
    swulling   304 天前   ♥ 1
    @OhYee stderr 在 Python3 是 line-buffered 的,在 Python2 是无 buffer 的
    从 print()看,你这个是 Python3 吧,一楼说的很清楚了
        13
    OhYee   304 天前
    @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
        14
    OhYee   304 天前
    @swulling
    python3 里不也是 stdout 是 line buffer,stderr 是无 buffer 么

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

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

    另外 13 楼的输出被截断也是 buffer 的问题么?
        15
    OhYee   304 天前
    @kkk330

    几种执行方式的结果来看

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

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



    执行结果:
    ```
    [email protected]:~$ python3 test. py
    stdout1 stdout2
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    stderr1 stderr2 [email protected]:~$
    [email protected]:~$
    [email protected]:~$ python3 -u test. py
    stdout1 stdout2
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    stderr1 stderr2 [email protected]:~$
    [email protected]:~$
    [email protected]:~$ python3 test. py >log. txt
    stderr1 stderr2 [email protected]:~$
    [email protected]:~$ cat log. txt
    stdout1 stdout2
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    [email protected]:~$ python3 test. py &>log. txt
    [email protected]:~$ cat log. txt
    stderr1 stderr2 stdout1 stdout2
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ```
        16
    kkk330   304 天前
    "你应该是直接在终端里执行的命令, 因为是 tty, 所以属于交互式的, 所以是行缓冲"

    你在 15 楼贴的就是"在终端里执行的命令"
        17
    whileFalse   304 天前
    @kkk330 学习了 谢谢
        18
    OhYee   304 天前
    @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 是无缓冲类似的解释
        19
    swulling   304 天前   ♥ 1
    @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.

    你不懂英文么?
        20
    OhYee   304 天前
    @kkk330 @swulling

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

    另外关于-u 会截断输出内容的方面有什么解释么?
        21
    swulling   304 天前   ♥ 1
    @OhYee -u 要看哪个版本,如果是 Python 3 但是是 3.7 以下,那么 -u 也不是 unbuffered,还是 line-buffered
    如果是 3.7+,那么
    Changed in version 3.7: The text layer of the stdout and stderr streams now is unbuffered.
        22
    OhYee   304 天前
    @swulling
    感谢,试了下 python3.6.7 没有问题,python3.7.1 存在这个问题
    看了下两者的区别,应该就是 unbuffered 导致被截断掉了

    此贴完结,感谢几位大佬的帮助

    ————————————

    Python3.6

    https://docs.python.org/3.6/using/cmdline.html#cmdoption-u
    -u
    Force the binary layer of the stdout and stderr streams (which is available as their buffer attribute) to be unbuffered. The text I/O layer will still be line-buffered if writing to the console, or block-buffered if redirected to a non-interactive file.

    See also PYTHONUNBUFFERED.

    https://docs.python.org/3.6/using/cmdline.html#envvar-PYTHONUNBUFFERED
    PYTHONUNBUFFERED
    If this is set to a non-empty string it is equivalent to specifying the -u option.



    Python3.7

    https://docs.python.org/3.7/using/cmdline.html#cmdoption-u
    -u
    Force the stdout and stderr streams to be unbuffered. This option has no effect on the stdin stream.

    See also PYTHONUNBUFFERED.

    Changed in version 3.7: The text layer of the stdout and stderr streams now is unbuffered.

    https://docs.python.org/3.7/using/cmdline.html#envvar-PYTHONUNBUFFERED
    PYTHONUNBUFFERED
    If this is set to a non-empty string it is equivalent to specifying the -u option.
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2297 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 24ms · UTC 14:59 · PVG 22:59 · LAX 06:59 · JFK 09:59
    ♥ Do have faith in what you're doing.