跳到正文
效率工具实用笔记关于本站
文件整理

SpooledTemporaryFile的max_size与rollover:数据什么时候落到磁盘

约 10 分钟阅读AI 辅助整理

SpooledTemporaryFile默认把数据留在内存,只有文件大小超过max_size或调用fileno()时才写入磁盘;rollover()可以不等大小超限就手动触发落盘。

本文要点

  1. max_size 这个参数容易被理解错

  2. fileno() 出现在触发条件里,是因为它要返回一个真正的文件描述符

  3. rollover() 解决的是“我想现在就让数据落到磁盘”这个需求

SpooledTemporaryFile的max_size与rollover:数据什么时候落到磁盘的知识要点示意图
依据已审核正文摘取要点制作的示意图,非实物照片;请结合正文中的条件与范围阅读。要点摘自已审核正文;完整条件与说明见正文。查看高清图解 ↗
本文目录

先回答:两个触发条件,加一个手动开关

如果你用 SpooledTemporaryFile 暂存数据,想知道它什么时候从内存转到磁盘,答案有三个入口。

按 Python 官方 tempfile 文档的说明,这个类的行为与 TemporaryFile() 完全一致,区别在于:数据会先在内存中暂存(spooled in memory),直到文件大小超过 max_size,或者文件的 fileno() 方法被调用,此时内容才被写入磁盘,之后的操作就和 TemporaryFile() 一样了。

文档还写明,这个对象额外带一个 rollover() 方法,它会让文件“无论大小”都转为磁盘文件。

所以判断顺序可以这样定:

1. 写入的数据量有没有超过 max_size?超过就落盘。

2. 有没有调用 fileno()?调用了就落盘。

3. 有没有主动调用 rollover()?调用了就落盘,不看大小。

三个条件任意一个成立,数据就从内存转到磁盘。本文只讲这三个触发条件,不讲性能调优。

一个前提:下面所有示例都是自建的练习脚本,只操作练习目录,不碰你正在用的文件。动手前建议在练习目录里新建脚本,例如 exercise/spool_demo.py。

max_size 是内存上限,不是磁盘上限

max_size 这个参数容易被理解错。它限制的是“数据在内存里能待多久”,不是“文件最大能有多大”。

按文档的表述,数据在内存中暂存,直到文件大小超过 max_size。也就是说,max_size 是内存阶段的容量阈值。一旦写入量越过这条线,内容就被写到磁盘,之后继续写多少都不再受这个值约束——因为此时它已经是一个磁盘文件了。

文档给出的默认值是 max_size=0。这一点值得单独记一笔:默认值不是某个较大的数字,而是 0。所以如果你不显式传 max_size,任何一次写入都会立刻超过它,数据基本不会在内存里停留。

一个自建示例(练习数据,不是对真实文件的记录):

import tempfile

with tempfile.SpooledTemporaryFile(max_size=16) as f:
    f.write(b"12345678")
    print(f._file)

这里 max_size 设成 16,写入 8 个字节,没有超过上限,所以此时底层还是内存对象。文档说明,返回对象的 _file 属性要么是 io.BytesIO 或 io.TextIOWrapper(取决于二进制还是文本模式),要么是一个真正的文件对象,取决于 rollover() 有没有被调用。

需要说清的是:文档没有规定“超过”是严格大于还是大于等于,也没有给出各平台内存缓冲的具体实现细节,本文不补这些。

fileno() 为什么也算触发条件

fileno() 出现在触发条件里,是因为它要返回一个真正的文件描述符。

按文档的表述,数据在内存中暂存,直到文件大小超过 max_size,或者文件的 fileno() 方法被调用,此时内容被写入磁盘。

这句话的逻辑是:内存里的 BytesIO 对象没有操作系统层面的文件描述符,而 fileno() 的职责就是交出这个描述符。所以一旦你调用它,对象就必须先变成一个真正的磁盘文件,才能给出有效的返回值。这就是为什么调用 fileno() 会连带触发落盘,而不是只返回一个编号。

一个自建示例(练习场景,不是对真实文件的记录):

import tempfile

with tempfile.SpooledTemporaryFile(max_size=1024) as f:
    f.write(b"small")
    print(f._file)
    fd = f.fileno()
    print(f._file)

第一次打印时数据量远小于 1024,底层还是内存对象;调用 fileno() 之后再打印,底层已经变成真正的文件对象。

这里要提醒一点:文档没有说明 fileno() 在文本模式下是否同样可用、返回值在哪些平台上有什么差异,本文不替它补这些结论。如果你只是想知道“有没有落盘”,看 _file 的类型比看 fileno() 的返回值更直接。

rollover() 是手动开关:不看大小,直接落盘

rollover() 解决的是“我想现在就让数据落到磁盘”这个需求。

按文档的表述,rollover() 会让文件无论大小都转为磁盘文件。注意“regardless of its size”这个限定:它不检查当前数据量有没有超过 max_size,你调用它就执行。

它和另外两个触发条件的区别在于主动性:

- 超过 max_size 是被动的,由写入量决定。

- 调用 fileno() 是间接的,你本来可能只是想要一个描述符。

- 调用 rollover() 是直接的,你就是想让它落盘。

一个自建示例(练习数据,不是对真实文件的记录):

import tempfile

with tempfile.SpooledTemporaryFile(max_size=1024) as f:
    f.write(b"tiny")
    print(f._file)
    f.rollover()
    print(f._file)

写入量很小,本来不会触发落盘;调用 rollover() 之后,底层对象类型发生变化。

一个边界:文档只说明 rollover() 会让文件转为磁盘文件,没有说明重复调用它会怎样、也没有说明调用后原有数据如何迁移的细节,本文不补这部分。

怎么核对数据到底在内存还是磁盘

判断落盘有没有发生,最直接的办法是看 _file 属性的类型,而不是靠猜。

文档明确写了这个属性的取值:要么是 io.BytesIO 或 io.TextIOWrapper(取决于二进制还是文本模式),要么是一个真正的文件对象,取决于 rollover() 有没有被调用。

所以核对方法可以这样组织(以下为编辑整理的观察方法,不是对某个真实程序的测试记录):

1. 在写入前后分别打印 _file,比较类型有没有变。

2. 类型是 BytesIO 或 TextIOWrapper,说明还在内存阶段。

3. 类型变成真正的文件对象,说明已经落盘。

一个自建对照表(示例,不是实测数据):

| 操作 | 预期 _file 类型 |

| 写入量小于 max_size | 内存对象 |

| 写入量超过 max_size | 文件对象 |

| 调用 fileno() 后 | 文件对象 |

| 调用 rollover() 后 | 文件对象 |

需要提醒的是:_file 带下划线前缀,文档把它作为属性来描述,但它不是为日常调用设计的公开接口。用它来观察行为可以,不要把它当成稳定的编程接口去依赖。

用 with 语句时,落盘时机和清理时机是两件事

落盘和清理容易被混在一起,但它们回答的是不同问题。

落盘回答的是“数据现在存在哪里”,由 max_size、fileno()、rollover() 三个条件决定。清理回答的是“这个临时文件什么时候消失”,由上下文管理器或对象销毁决定。

文档说明,TemporaryFile、NamedTemporaryFile、TemporaryDirectory 和 SpooledTemporaryFile 都是提供自动清理、可作为上下文管理器使用的高层接口。对 SpooledTemporaryFile 来说,它“与 TemporaryFile() 完全一致”,所以同样遵循这套清理规则。

也就是说,即使数据已经落到磁盘,退出 with 语句时它仍然会被清理掉。落盘不等于留下文件。

一个自建示例(练习场景,不是对真实文件的记录):

import tempfile

with tempfile.SpooledTemporaryFile(max_size=8) as f:
    f.write(b"0123456789")
    print(f._file)

写入 10 个字节,超过 max_size=8,触发落盘。退出 with 之后,这个临时文件按高层接口的规则被清理。

一个边界:文档没有说明落盘后文件在文件系统中的可见性,也没有给出具体路径,本文不补这些内容,也不建议你去按路径查找它。

使用限制与参考资料

本文只讲 SpooledTemporaryFile 在内存与磁盘之间切换的三个触发条件,以及 rollover() 的作用。以下几点是资料没有支持、因此本文没有展开的内容:

- 没有给出不同 max_size 取值下的性能差异,也没有建议取多大合适。

- 没有说明落盘后文件的具体位置和命名规则。

- 没有说明文本模式与二进制模式下触发行为是否有差异。

- 没有说明多线程或多进程场景下的行为。

操作提醒:本文所有示例都只操作练习目录里的自建脚本,不读写你正在使用的文件。运行前建议在练习目录里新建文件,例如 exercise/spool_demo.py,不要拿重要数据直接试。

事实来源:Python 官方 tempfile 模块文档,https://docs.python.org/3/library/tempfile.html

观看知识要点视频 18 秒

18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。

参考资料

以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。

请结合原始资料的适用条件阅读。

返回文件整理 →