当前位置: 首页 > news >正文

【Python Cookbook】文件与 IO(二)

文件与 IO(二)

  • 6.字符串的 I/O 操作
  • 7.读写压缩文件
  • 8.固定大小记录的文件迭代(⭐⭐)

6.字符串的 I/O 操作

你想使用操作类文件对象的程序来操作文本或二进制字符串。

使用 io.StringIO()io.BytesIO() 类来创建类文件对象操作字符串数据。比如:

>>> s = io.StringIO()
>>> s.write('Hello World\n')
12
>>> print('This is a test', file=s)
15
>>> # Get all of the data written so far
>>> s.getvalue()
'Hello World\nThis is a test\n'
>>>>>> # Wrap a file interface around an existing string
>>> s = io.StringIO('Hello\nWorld\n')
>>> s.read(4)
'Hell'
>>> s.read()
'o\nWorld\n'
>>>

io.StringIO 只能用于文本。如果你要操作二进制数据,要使用 io.BytesIO 类来代替。比如:

>>> s = io.BytesIO()
>>> s.write(b'binary data')
>>> s.getvalue()
b'binary data'
>>>

当你想模拟一个普通文件的时候,StringIOBytesIO 类是很有用的。比如,在单元测试中,你可以使用 StringIO 来创建一个包含测试数据的类文件对象,这个对象可以被传给某个参数为普通文件对象的函数。

需要注意的是, StringIOBytesIO 实例并没有正确的整数类型的文件描述符。因此,它们不能在那些需要使用真实的系统级文件,如文件,管道或者是套接字的程序中使用。

🚀 在博主的另一篇博客《解析 io.StringIO 与 io.BytesIO》中有更为详细的介绍。

7.读写压缩文件

你想读写一个 gzipbz2 格式的压缩文件。

gzipbz2 模块可以很容易的处理这些文件。 两个模块都为 open() 函数提供了另外的实现来解决这个问题。比如,为了以文本形式读取压缩文件,可以这样做:

# gzip compression
import gzip
with gzip.open('somefile.gz', 'rt') as f:text = f.read()# bz2 compression
import bz2
with bz2.open('somefile.bz2', 'rt') as f:text = f.read()

类似的,为了写入压缩数据,可以这样做:

# gzip compression
import gzip
with gzip.open('somefile.gz', 'wt') as f:f.write(text)# bz2 compression
import bz2
with bz2.open('somefile.bz2', 'wt') as f:f.write(text)

如上,所有的 I/O 操作都使用文本模式并执行 Unicode 的编码/解码。类似的,如果你想操作二进制数据,使用 rb 或者 wb 文件模式即可。

大部分情况下读写压缩数据都是很简单的。但是要注意的是选择一个正确的文件模式是非常重要的。如果你不指定模式,那么默认的就是二进制模式,如果这时候程序想要接受的是文本数据,那么就会出错。gzip.open()bz2.open() 接受跟内置的 open() 函数一样的参数,包括 encodingerrorsnewline 等等。

当写入压缩数据时,可以使用 compresslevel 这个可选的关键字参数来指定一个压缩级别。比如:

with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:f.write(text)

默认的等级是 9 9 9,也是最高的压缩等级。等级越低性能越好,但是数据压缩程度也越低。

最后一点, gzip.open()bz2.open() 还有一个很少被知道的特性,它们可以作用在一个已存在并以二进制模式打开的文件上。比如,下面代码是可行的:

import gzip
f = open('somefile.gz', 'rb')
with gzip.open(f, 'rt') as g:text = g.read()

这样就允许 gzipbz2 模块可以工作在许多类文件对象上,比如套接字,管道和内存中文件等。

8.固定大小记录的文件迭代(⭐⭐)

你想在一个固定长度记录或者数据块的集合上迭代,而不是在一个文件中一行一行的迭代。

通过下面这个小技巧使用 iterfunctools.partial() 函数:

from functools import partialRECORD_SIZE = 32with open('somefile.data', 'rb') as f:records = iter(partial(f.read, RECORD_SIZE), b'')for r in records:...
  • partial(f.read, RECORD_SIZE):
    • functools.partial 创建一个新的函数,这个新函数会固定 f.read 的第一个参数为 RECORD_SIZE(即 32)。
    • 相当于每次调用 partial(f.read, RECORD_SIZE)() 都会执行 f.read(32),即从文件中读取 32 字节的数据。
  • iter(callable, sentinel):
    • iter 不仅可以用于可迭代对象,还可以接受一个可调用对象(callable)和一个哨兵值(sentinel)。
    • 它会重复调用 callable,直到返回 sentinel 为止,此时迭代停止。
    • 在这里,callablepartial(f.read, RECORD_SIZE)sentinelb''(空字节串)。
    • 因此,iter 会不断调用 f.read(32),直到返回空字节串(表示文件读取完毕),然后停止迭代。
  • for r in records::
    • records 是一个迭代器,每次迭代会返回一个最多 RECORD_SIZE 字节的记录(r)。
    • 当文件读取完毕时,f.read(32) 返回 b'',迭代终止。

这个例子中的 records 对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾。要注意的是如果总记录大小不是块大小的整数倍的话,最后一个返回元素的字节数会比期望值少。

iter() 函数有一个鲜为人知的特性就是,如果你给它传递一个可调用对象和一个标记值,它会创建一个迭代器。这个迭代器会一直调用传入的可调用对象直到它返回标记值为止,这时候迭代终止。

在例子中, functools.partial 用来创建一个每次被调用时从文件中读取固定数目字节的可调用对象。标记值 b'' 就是当到达文件结尾时的返回值。

最后再提一点,上面的例子中的文件是以二进制模式打开的。如果是读取固定大小的记录,这通常是最普遍的情况。而对于文本文件,一行一行的读取(默认的迭代行为)更普遍点。

🚀 假设 somefile.data 的内容是 b'HelloWorld' * 10(即重复 10 次的 b'HelloWorld',共 100 字节),并且 RECORD_SIZE = 32

文件内容

b'HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld'

运行代码

from functools import partialRECORD_SIZE = 32with open('somefile.data', 'rb') as f:records = iter(partial(f.read, RECORD_SIZE), b'')for i, r in enumerate(records, 1):print(f"Record {i}: {r}")

输出

Record 1: b'HelloWorldHelloWorldHelloWorldHello'
Record 2: b'WorldHelloWorldHelloWorldHelloWorld'
Record 3: b'HelloWorldHelloWorldHelloWorldHello'
Record 4: b'World'

说明

  • 文件共100字节,每次读取32字节:
    • 第1次读取:32字节(b'HelloWorldHelloWorldHelloWorldHello'
    • 第2次读取:32字节(b'WorldHelloWorldHelloWorldHelloWorld'
    • 第3次读取:32字节(b'HelloWorldHelloWorldHelloWorldHello'
    • 第4次读取:剩余4字节(b'World'
    • 第5次读取:返回 b'',迭代终止。

适用场景

这种方法非常适合处理固定大小的记录文件,例如:

  • 二进制文件格式(如数据库文件、图像文件等)。
  • 网络协议数据包(固定大小的数据块)。
  • 任何需要分块处理的流式数据。
http://www.xdnf.cn/news/754831.html

相关文章:

  • unix/linux source 命令,其历史争议、兼容性、生态、未来展望
  • 从前端工程化角度解析 Vite 打包策略:为何选择 Rollup 而非 esbuild。
  • 鸿蒙OSUniApp集成WebGL:打造跨平台3D视觉盛宴#三方框架 #Uniapp
  • C# await与wait的区别
  • 核函数:解锁支持向量机的强大能力
  • NodeJS全栈开发面试题讲解——P3数据库(MySQL / MongoDB / Redis)
  • NLP学习路线图(十五):TF-IDF(词频-逆文档频率)
  • BFS入门刷题
  • Python程序的文件头部声明小结
  • day16 leetcode-hot100-32(链表11)
  • 操作系统学习(十)——文件系统
  • WEB3——开发者怎么查看自己的合约日志记录
  • vscode编辑器怎么使用提高开发uVision 项目的效率,如何编译Keil MDK项目?
  • AI大模型赋能,aPaaS+iPaaS构建新一代数智化应用|爱分析报告
  • Leetcode 3569. Maximize Count of Distinct Primes After Split
  • 刷leetcode hot100--矩阵6/1
  • Java虚拟机内存区域划分
  • 数据库系统概论(十)SQL 嵌套查询 超详细讲解(附带例题表格对比带你一步步掌握)
  • 【寻找Linux的奥秘】第九章:自定义SHELL
  • 【深度学习】 19. 生成模型:Diffusion Models
  • 爬虫入门:从基础到实战全攻略
  • JavaEE: wait和notify
  • debian12.9或ubuntu,vagrant离线安装插件vagrant-libvirt
  • 网络协议的原理及应用层
  • 聊一聊接口测试中缓存处理策略
  • 【萌笔趣棋】网页五子棋项目测试报告
  • 零基础上手 Cherry Studio:打造专属 AI 助手的第一步
  • 【Vue 3全栈实战】从组合式API到企业级架构设计
  • 内网怎么映射外网ip? 内网的地址快速映射给外网访问用方法
  • uni-app学习笔记二十--pages.json页面路由pages设置