Python scandir 元数据缓存:文件已经变大,旧 DirEntry 为什么仍报原来的大小

前天 3阅读

扫描目录后把 DirEntry 对象存进列表,过一会儿再用它们统计大小,结果可能一直停在旧数字。文件确实已经被写大,目录项也不是没找对;关键在于 DirEntry 会缓存取得的部分元数据,重复调用它的方法不等于每次重新查询文件系统。

先填充缓存,再改变文件

下面只创建一个临时文件,先写入一个字节,随后从扫描结果调用 stat,明确建立本次实验要观察的缓存。再把同一路径改写成六个字节,同时比较旧对象的 stat 和 os.stat。顺序固定,不依赖修改时间精度,也不用等待一秒。

保存为 demo.py,执行 python demo.py。with 负责关闭扫描迭代器占用的资源;将 entry 留下来只是为了复现长时间保存目录项的用法。文件目录是本例独有的,避免把其他进程的并发修改混入结果。

Python scandir 元数据缓存:文件已经变大,旧 DirEntry 为什么仍报原来的大小

AI概念示意图:旧目录项保留先前记录的属性,新查询才会观察后来发生的变化。

import os
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    root = Path(folder).resolve()
    file = root / "sample.txt"
    file.write_bytes(b"A")
    with os.scandir(root) as entries:
        entry = next(entries)
        first = entry.stat().st_size
    assert first == 1

    file.write_bytes(b"abcdef")
    cached = entry.stat().st_size
    fresh = os.stat(entry.path).st_size
    assert cached == 1 and fresh == 6
    print("old entry:", cached, "fresh stat:", fresh)

    with os.scandir(root) as entries:
        rescanned = next(entries).stat().st_size
    assert rescanned == 6
    print("new entry:", rescanned)

    file.unlink()
    assert entry.stat().st_size == 1
    print("cached after removal:", entry.stat().st_size)
    try:
        os.stat(entry.path)
    except FileNotFoundError:
        print("fresh after removal: FileNotFoundError")
    else:
        raise AssertionError("removed file unexpectedly exists")

第一行的 old entry 为一,fresh stat 为六;重新扫描获得的新对象也返回六。这里没有争论文件究竟有多大,而是确认这两次读取来自不同观察时刻。原来的 stat_result 值不会随着文件写入自动更新。

缓存可能在路径消失以后继续返回

示例删除自己的样本文件后,旧 entry.stat 仍能返回已缓存的一字节;新的 os.stat 则抛出 FileNotFoundError。这个反例更容易暴露错误:拿到一个属性结果,并不足以证明该路径现在仍然存在。

不能因此总结为 DirEntry 永远不会报错。尚未缓存的信息仍可能需要系统调用,查询时就可能遇到文件消失、权限不足等错误。缓存命中与否改变了观察路径,实际扫描程序应按调用处处理异常,保留未完成项。

如果知道元数据已改变,或者扫描和使用之间相隔较久,就通过 os.stat(entry.path) 重新获取。需要保存长期清单时,可以保存路径和采集时间,并把采集到的大小明确当作历史记录;不要把目录项对象当成会自动刷新的文件模型。

新查询仍然只是一次观察

重新查询解决旧缓存问题,却不能锁住路径。查询结束后,别的进程仍可能替换或删除文件。若后续需要处理同一个已经打开的文件,应围绕打开得到的文件描述符操作,并按需要用 fstat 检查它;一次 stat 不是事务或并发保护。

符号链接还要明确跟随策略。DirEntry.stat 默认跟随链接,关闭跟随时检查的是链接本身;两种策略有分别缓存的结果。真实扫描应在类型判断和属性查询中保持一致,不能用链接本身的属性去解释目标文件的内容。

scandir 的好处之一,是复用目录枚举已经提供的信息,减少部分额外查询。不同平台和文件系统能直接提供的属性不同,所以本文没有声称每种方法都零系统调用,也没有根据一个小文件推导固定的速度提升比例。

entry.path 还保留扫描入口的路径形式。若入口使用相对路径,后来改变工作目录,同一串文字可能解析到别处。本例先把临时根目录转为绝对路径;长期任务也应在保存时明确路径基准,避免把位置变化误认为缓存错误。

验收时保留改写与删除两类样本,分别检查“旧值仍在”和“新查询会失败”。清单展示可接受有时间标记的快照;后续处理若要求当前状态,就在对应步骤重新确认,并把查询失败作为正常可能发生的分支。

参考资料

资料核对日期:2026年10月2日(北京时间)。示例在 Linux、CPython 3.12.14 中独立运行。

Python:os.DirEntry 的缓存与生命周期

Python:os.stat 与文件描述符查询

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。