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

推荐订阅源

H
Help Net Security
大猫的无限游戏
大猫的无限游戏
雷峰网
雷峰网
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
V
Visual Studio Blog
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
有赞技术团队
有赞技术团队
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
博客园 - 三生石上(FineUI控件)
WordPress大学
WordPress大学
Vercel News
Vercel News
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
小众软件
小众软件
月光博客
月光博客
A
About on SuperTechFans

OhYee 博客

小鹏辅助驾驶测评|OhYee 博客 小鹏非支持手机开启自动解锁|OhYee 博客 使用函数计算实现 301 重定向|OhYee 博客 针对 HTML 内容使用 Ant Design 图片弹框|OhYee 博客 博客进程泄露及僵尸进程解决|OhYee 博客 蓝易云服务器体验|OhYee 博客 SSH 调起本地 VSCode|OhYee 博客 【2022 秋招内推】阿里云后端研发工程师|OhYee 博客 使用函数计算获取 IP 地址信息|OhYee 博客 正确获取客户端 IP/HTTP Header 也可能重复|OhYee 博客 评测 Oculus Quest2 及 BigScreen|OhYee 博客 NextJS 热重载保留状态|OhYee 博客 如何优雅地贴 gist 代码|OhYee 博客 Linux 精细化文件权限|OhYee 博客 VSCode 容器开发环境|OhYee 博客 Clash 的不兼容更新排查|OhYee 博客 Zeek 导出 PCAP|OhYee 博客 记一次 ssh 配置问题|OhYee 博客 Git Commit 规范化工具|OhYee 博客 谈谈《星之卡比-探索发现》|OhYee 博客 VSCode 快捷键绑定 Shell 命令|OhYee 博客 ASN.1 语法及 X.509 证书格式解析解析|OhYee 博客 腾讯企业邮箱忽略 MX 记录发信|OhYee 博客 Chrome/Edge 标签组插件|OhYee 博客 【应届内推】阿里云后端研发工程师|OhYee 博客 损坏的 Typecho 备份处理为 JSON|OhYee 博客 VS Code VIM 插件高效使用|OhYee 博客 SSH 正反向代理|OhYee 博客 Let's Encrypt 根证书过期引发的问题|OhYee 博客 OpenWRT 忽略内核依赖|OhYee 博客
Python的Buffer机制|OhYee 博客
2019-01-22 · via OhYee 博客

一个意外的特性(Bug)引起的对Python Buffer机制的研究

这是一篇最后编辑于 7 年前 的文章,其内容可能与目前实际情况差异较大,请注意甄别

环境准备

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

cmd清空

set PYTHONUNBUFFERED=""

powershell清空

$env:PYTHONUNBUFFERED=""

bash清空

export PYTHONUNBUFFERED=""

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

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

当将其改为

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操作次数

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

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?

参考资料