惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
V
V2EX
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
月光博客
月光博客
爱范儿
爱范儿
D
Docker
U
Unit 42
P
Proofpoint News Feed
I
InfoQ
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LangChain Blog
V
Visual Studio Blog
IT之家
IT之家
Vercel News
Vercel News
G
Google Developers Blog
M
MIT News - Artificial intelligence
美团技术团队
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog

WoodenRobot's Blog

Mac OS 系统无法双击使用 Chrome 浏览器打开 HTML 文件 群晖 SSH 公钥免密登录 群晖 Moments 人物识别一键修复解决方案 获取联通友华 PT952G 光猫超级密码和宽带拨号账号密码 去除 iTerm2 下 Tmux 复制模式警告 优化 oh my zsh 启动速度 优酷路由宝 YK-L1c 和 YK-L1 刷入 Breed 不死和 hiboy Padavan 固件 Zoom 直播分享 Awesome pipeline 录像和资料下载 Python 使用 Redis 实现分布式锁 [转]Python 相对导入与绝对导入 Mac 使用 Brew 升级 openssl@1.1 问题 Linux通过源码编译安装 Python3.8 家庭网络漫游指南 修复黑群晖 Moments 1.3.3-0700 版本人物识别不能使用问题 TOTOLINK A3004NS 国行刷入 Breed 不死和 hiboy Padavan 固件 Nginx 搭建 Google 镜像站 中兴 ZXHN F677V2 光猫改桥接 群晖 Video Station 支持 DTS 和 eac3 解决方案 浏览器输入 URL 后的历程
[转] Python 的 Buffer 机制
本文作者: WoodenRobot · 2019-08-20 · via WoodenRobot's Blog

本文转载于:Python的Buffer机制

环境准备

Python版本: 3.7.1(3.7以上版本)
清空 PYTHONUNBUFFERED 环境变量(默认是空的,不过以防万一还是清空下)

cmd 清空

1
set PYTHONUNBUFFERED=""

powershell 清空

1
$env:PYTHONUNBUFFERED=""

bash 清空

1
export PYTHONUNBUFFERED=""

将下面内容保存到 test.py,执行下面的语句

1
2
3
4
5
import sys
sys.stdout.write("stdout1 ")
sys.stderr.write("stderr1 ")
sys.stdout.write("stdout2 ")
sys.stderr.write("stderr2 ")

执行 python test.py, 输出 stderr1 stderr2 stdout1 stdout2
执行 python -u test.py, 输出 stdout1 stderr1 stdout2 stderr2

缓冲区

现象解释

对于 stderr,其名称是标准错误输出文件,标准里规定是无缓冲的,每次输出一个字符就直接显示到屏幕上
而对于 stdout,其名称是标准输出文件,UNIX标准里规定是行缓冲的,遇到换行或者积累到一定的大小一次性输出到屏幕上

由于默认情况下,缓冲区是开启的。
因此 stdout 的输出会先存入到一个 buffer 中,而 stderr 的内容是直接显示的,因此默认输出顺序是 stderr1 stderr2 stdout1 stdout2

当将其改为

1
2
3
4
5
import sys
sys.stdout.write("stdout1\n")
sys.stderr.write("stderr1\n")
sys.stdout.write("stdout2\n")
sys.stderr.write("stderr2\n")

由于 stdout 是行缓冲,遇到换行后也是直接输出,因此此时输出内容就是正常顺序

对于 Python 而言,其规定如下:

  • 对于 Python2.x: stdout 行缓存, stderr 是无缓冲(和规范相同)
  • 对于 Python3.x: stdout 和 stderr 都是行缓冲

关于该改动的讨论: Improve documentation of stdout/stderr buffering in Python 3.x

而对于 -u 参数:

  • 对于 3.6 以下版本: 二进制流使用 unbuffered (不使用 buffer ),但是文本流采用 line-buffer(3.6 相关文档)
  • 对于 3.7 以上版本: 全部采用 unbuffered(3.7 相关文档)

而当 PYTHONPATH 不为空,视为使用了 -u 参数

虽然输出结果与行缓冲与无缓冲的结果相同,但是仍然需要注意这里并非是如同规范那样定义

什么时候不应该使用缓冲区

一般来说,第一次接触缓冲区应该都是C语言读入部分

比如如果要有用户交互输入数据,需要分析输入内容,可能会牵扯到换行的处理

当用户一次性输入很多数据时,这些数据都会被存放在输入缓冲区内,每次使用 getchar();或者 scanf("%c", &c);会从中取出一个字符出来

这时,如果输入末尾有无效输入,比如用户多打了个空格才换行等奇怪的操作,可能会导致输入缓冲区留下一部分数据,从而导致下一次处理数据出错

这时,往往要使用 fflush(stdin);在每次读入前清空缓冲区,以确保读到的数据是用户刚刚输入的内容

在 Python 中,最常见的操作是使用 flask、django 时,后台打印 log。

当同时有两个请求送达,两个打印 log 的操作同时写入输出缓冲区,很可能你看到的就是两个 log 混杂在一起的内容,这时为了保证 log 的完整以及有序,就应该关闭缓冲区

而在Python中,关闭缓冲区有三种方法:

  • sys.stdout.flush()
  • python -u xxx.py
  • set PYTHONUNBUFFERED=""

什么时候应该使用缓冲区

缓冲区(buffer)原本是用于中和内外存读取速度不同而设计的一个缓冲

因此当读写速度与运算、处理速度不匹配时就应该使用缓冲区

如大量写入文件,尽管可以使用 "n".join(data_list) 来将数据拼接成文本一次写入,但是有时候可能会由于异步、数据结构过于复杂,需要用多个 f.write() 来写入文件,这时就需要使用 buffer

将要写到文件的数据先存入内存中,关闭文件时一次性写入,从而减少了 io 操作次数

另外,引起该篇博文的原因代码是:

1
print(";".join([str(i) for i in range(10000)]))

这段代码在 Windows 环境下的 Python3.9 以下版本使用 python -u test.py 会导致奇怪的截断

简单解释就是 Windows 7 以下的控制台对写出有限制(64KiB),因此 python 会尝试将输出内容除以 2 来输出
最终导致输出内容被截断

该“特性”将在Python3.9被移除
相关讨论: StackOverflow: Why does this code print a different result between Windows and Linux?

参考资料