Python mmap 写时复制:视图已经改成新值,为什么 flush 后文件仍然没变
做二进制格式试改时,内存映射允许像修改字节数组一样修改文件内容。但映射里看见新值,不等于源文件也被保存了。决定写回行为的是创建映射时的访问模式,flush不能把一份写时复制映射临时切换成可写文件。
下面的完整实验适用于支持mmap的常规CPython桌面或服务器环境,已在Linux、CPython 3.12.14实跑。保存为demo.py并运行python3 demo.py,只创建自动清理的临时文件。样本不是空文件,三种映射依次关闭后再创建,避免把并发映射或跨平台删除规则掺进观察。
AI模型生成概念图:上层修改页与下层原始页分开,表示写时复制视图的修改不会自动落到源文件;图片不是程序截图。
把同一处字节交给三种访问模式
先写入MODE=blue和换行,并刷新Python文件对象的缓冲区。随后只替换四个字节,gold和blue长度相同,因此不会涉及映射扩容。程序既查看view中的内容,也回到文件对象读取源数据,两条观察路径各自承担一个断言。
完整可运行程序
import mmap
from tempfile import TemporaryFile
original = b"MODE=blue\n"
with TemporaryFile(mode="w+b") as file:
file.write(original)
file.flush()
with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_COPY) as view:
view[5:9] = b"gold"
assert view[:] == b"MODE=gold\n"
view.flush()
file.seek(0)
stored = file.read()
assert stored == original
print("copy view:", view[:])
print("file after copy flush:", stored)
with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_WRITE) as view:
view[5:9] = b"rose"
view.flush()
file.seek(0)
assert file.read() == b"MODE=rose\n"
print("file after write flush:", b"MODE=rose\n")
with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as view:
try:
view[0] = ord("X")
except TypeError:
print("read-only assignment: TypeError")
else:
raise AssertionError("read-only mapping accepted assignment")本次实际输出
copy view: b'MODE=gold\n' file after copy flush: b'MODE=blue\n' file after write flush: b'MODE=rose\n' read-only assignment: TypeError
从四行输出判断写到了哪里
第一行里的gold证明ACCESS_COPY允许修改映射;第二行在调用view.flush之后仍是blue,证明这次修改没有进入文件。第三行换成ACCESS_WRITE以后变成rose。最后的TypeError则来自ACCESS_READ,说明只读映射连当前映射里的赋值也不接受。
这里的COPY不能理解为“暂时写在缓存里,稍后flush保存”。它选择的是私有修改语义。若程序只是想演练一次头部变更,这种模式有用;若目标就是更新文件,应在明确写入意图后选择正确的访问模式,不能靠多调用几次flush补救。
同名flush属于不同层次
代码开头的file.flush让刚写入的原始字节离开Python文件缓冲区,以便建立映射时读取。后面的view.flush作用于映射。二者面对的层次不同,不能仅凭方法名称相同就互换。
实验验证的是程序读回的内容,不是突然断电后的持久性保证。需要抗故障保存时还要按文件系统和业务要求设计同步、原子替换与恢复流程;mmap本身也不提供多字段事务。不要把一次可读回的结果直接当成完整提交协议。
映射长度和访问模式一起进入验收
这个例子只在既有长度内修改。ACCESS_COPY与ACCESS_READ不能用resize扩大映射;若用切片赋值替换不同数量的字节,也不能把它当作普通列表插入。生产程序应先校验目标区间和替换长度。
映射建立后,也不要让其他进程随意截短底层文件。本文没有用多个写入者做一致性测试,不能据此推断并发读写的可见顺序。排错时记录访问模式、文件长度、修改区间以及源文件读回值,比只保存一张映射内容截图更能说明问题。
参考资料
资料核验日期:2026年10月2日。代码输出来自本文固定输入的本地实跑。


