首页 / 资讯中心 / 文章详情

详解python中的IO操作方法

详解python中的IO操作方法 ★ FEATURED ARTICLE
前言提到 IO很多人脑子里只有「读文件、写文件」。但 Python 的io模块描述的是一整套流stream的分层模型最底层是原始字节流中间是带缓冲的二进制流最上面才是把文本编码成字节的文本流。open()返回的东西其实就是这三层拼起来的包装体。理解这个分层能解释很多「为什么」为什么open()只读模式返回的是io.BufferedReader写模式返回io.BufferedWriter为什么文本模式下seek的偏移量是个不透明的数为什么StringIO能直接丢给csv.reader用。还有一个官方文档到今天仍在强调的点open()和TextIOWrapper的默认编码是 locale 相关的locale.getencoding()大多数 Windows 用户的 locale 编码不是 UTF-8于是打开 UTF-8 文件读取非 ASCII 字符就会出问题。官方因此明确建议打开文本文件时显式指定编码。本文讲三层 IO 模型、内存里的流StringIO/BytesIO、包装与重定向以及怎么用官方提供的开关把「忘了写 encoding」这种问题主动暴露出来。示例面向 Python 3.8。一、IO 的三层结构io模块把流分成三类抽象基类层基类处理单位典型实现原始层io.RawIOBasebytesio.FileIO缓冲层io.BufferedIOBasebytesBufferedReader、BufferedWriter、BytesIO文本层io.TextIOBasestrTextIOWrapper、StringIO原始层直接对接系统调用read()可能只返回部分数据调用方要自己重试缓冲层在上面包了一层缓冲帮你处理「部分读写」返回值语义变得友好文本层再往上是编码/解码把字节变成str。open()的返回类型正是按这个模型决定的文本模式返回io.TextIOWrapper读二进制返回io.BufferedReader写二进制返回io.BufferedWriter。buffering参数控制缓冲策略# 适用于 Python 3.8open(path, rb, buffering0) # 关闭缓冲只允许二进制模式open(path, w, buffering1) # 行缓冲只在文本写入模式下可用open(path, rb, buffering65536) # 指定固定大小的块缓冲传buffering0只有在二进制模式才合法文本模式下会报错——因为文本层本身就依赖缓冲。二、内存里的流StringIO 与 BytesIO不是所有「文件」都得落在磁盘上。当某个函数接受一个文件对象时你完全可以用内存流顶替# 适用于 Python 3.8import iobuf io.StringIO(第一行\n第二行\n)print(buf.readline()) # 第一行\nprint(buf.read()) # 第二行\nbuf.seek(0)print(buf.getvalue()) # 拿到全部内容不移动当前位置io.StringIO(initial_value, newline\n)是文本内存流io.BytesIO(initial_bytesb)是二进制内存流# 适用于 Python 3.8import iob io.BytesIO()b.write(b\x00\x01\x02)print(b.getvalue()) # b\x00\x01\x02b.seek(0)print(b.read(2)) # b\x00\x01内存流最大的价值是让「写文件」的接口变成可测试的纯内存操作。例如csv.writer接受任何带write()方法的对象就可以把它接到StringIO上在内存里生成 CSV 再检查内容完全不碰磁盘# 适用于 Python 3.8import csvimport iobuf io.StringIO()writer csv.writer(buf)writer.writerow([姓名, 分数])writer.writerow([张三, 90])print(buf.getvalue())这里要提醒一句StringIO和真正的文件对象并不完全等价。它没有文件描述符没有fileno()也不能被select之类的系统接口使用。用它替换文件对象时先确认下游只调用了read/write/seek/getvalue这类通用方法。三、包装与重定向io.TextIOWrapper可以把一个二进制流包成文本流。它的完整签名是io.TextIOWrapper(buffer, encodingNone, errorsNone, newlineNone,line_bufferingFalse, write_throughFalse)一个实用场景是先用二进制方式拿到数据再按指定编码解包。这比「二进制读出来自己 decode 切行」要省事得多。另一个常见需求是把print的输出临时改道。contextlib.redirect_stdout就是干这个的# 适用于 Python 3.8import iofrom contextlib import redirect_stdoutbuf io.StringIO()with redirect_stdout(buf):print(这段字被重定向了)print(捕获到, buf.getvalue().strip())redirect_stdout只在with块内生效退出后sys.stdout自动还原。这类「配对操作」用上下文管理器写比手写sys.stdout buf再手动还原来得安全——后者的还原语句一旦被异常跳过整个进程的输出就都跑到缓冲区里去了。让「忘了写 encoding」主动报警Python 3.10 起提供了两个开关可以在代码用到默认 locale 编码时发出EncodingWarning# 适用于 Python 3.10# 运行时打开警告python -X warn_default_encoding your_script.py# 适用于 Python 3.10# 或者用环境变量PYTHONWARNDEFAULTENCODING1 python your_script.py同一版本还加入了encodinglocale这个特殊取值用来「显式地表示我要用当前 locale 编码」。另外io.text_encoding()是给库作者用的在自己的 API 里接收encodingNone时可以借它让调用方自动收到EncodingWarning。实战把文本处理函数改成可测试的下面这个函数不直接写磁盘而是接受一个「类文件对象」这样既能写文件也能写内存测试起来方便# 适用于 Python 3.8import iodef dump_pairs(pairs, stream):把 (键, 值) 序列写成 keyvalue 每行一条。for key, value in pairs:stream.write(f{key}{value}\n)if __name__ __main__:# 真实场景写文件with open(out.ini, w, encodingutf-8) as f:dump_pairs([(host, localhost), (port, 8080)], f)# 测试场景写内存buf io.StringIO()dump_pairs([(host, localhost), (port, 8080)], buf)assert buf.getvalue() hostlocalhost\nport8080\n这种「面向流编程」的写法是 IO 分层的直接收益同一份逻辑换个流对象就能跑在不同介质上。常见坑点1. 把StringIO当真实文件用❌io.StringIO().fileno()抛io.UnsupportedOperation。 ✅ 需要文件描述符就用tempfile.TemporaryFile()只是做数据缓冲就用StringIO。2.BytesIO和StringIO混用❌ 对BytesIO调write(文本)抛TypeError: a bytes-like object is required。 ✅ 二进制流写bytes文本流写str两者不要串。3.getvalue()与read()混淆❌ 想取全部内容却调了read()把游标推到了末尾后续读取全空。 ✅ 只取内容不动游标用getvalue()。4. 文本模式关缓冲❌open(path, w, buffering0)抛ValueError: cant have unbuffered text I/O。 ✅ 关闭缓冲只在二进制模式可用文本模式至少要有缓冲。5. 手写sys.stdout替换却不还原❌sys.stdout buf之后中途抛异常还原语句没执行整个程序输出消失。 ✅ 用with redirect_stdout(buf):退出时自动还原。6. 忘了写 encoding 却在 Windows 上测不出问题❌open(a.json)在中文 Windows 上用 cp936读 UTF-8 内容抛UnicodeDecodeError。 ✅ 始终写encodingutf-8排查阶段用-X warn_default_encoding打开警告。7. 原始流只读一次就当读完了❌ 以为RawIOBase.read()一次就能拿到全部数据结果拿到的是部分字节。 ✅ 用缓冲层open()默认就是或自己循环读到空为止。8. 缓冲没刷新就让别人读文件❌ 写完没关也没flush()另一个进程读到的还是旧内容。 ✅ 用with退出自动 flush或显式调用f.flush()。总结需求用什么关键点处理文本open(..., encodingutf-8)默认是 locale 编码跨平台不一致处理字节open(..., rb)不能传encoding内存文本流io.StringIO有getvalue()没有fileno()内存字节流io.BytesIO只能写bytes二进制转文本io.TextIOWrapper需要自己指定编码重定向输出contextlib.redirect_stdout上下文管理器自动还原排查编码-X warn_default_encodingPython 3.10触发EncodingWarningIO 这套分层设计的核心思想是「把编码、缓冲、系统调用拆成可以单独替换的层」。理解它以后open()返回什么类型、seek为什么在文本模式下受限、内存流为什么能顶替文件这些疑问都会变成同一个答案。
阅读完成 · 觉得有帮助?
咨询建站