fsspec 打开 tar 成员后报 seek of closed file,外层对象不能先释放
链式 tar URL 的成员句柄仍依赖归档所有者。保留 OpenFile 覆盖整个读取范围,处理 pandas 延迟消费与手动关闭,避免把句柄返回后留下已关闭的底层文件。
fsspec.open(url, mode="rb").open() 返回了文件对象,第一次读取却报 ValueError: seek of closed file。URL 是 tar://data.csv::file:///.../demo.tar 这种链式归档路径时,先保留外层 OpenFile 的生命周期,而不是立刻判断 tar 已损坏。
fsspec 2026.4.0 的清理变化让这类用法暴露出关闭问题:临时的外层对象没有保存,归档文件系统可能在成员文件仍被使用时就被回收。2026.9.0 的 OpenFile.open() 仍明确要求调用方在使用实际文件期间保留 OpenFile 引用。
成员句柄仍然依赖外层归档
fsspec.open() 返回的首先是一个描述如何打开文件的对象。再调它的 .open() 才取得实际句柄。对链式 tar URL,最后读到的成员还需要访问外层归档;保住成员变量本身,不一定保住整个资源所有者。
因此,下面这种临时对象写法不适合作为长期持有句柄的方式:
fh = fsspec.open(url, mode="rb").open()
错误可能直到 .read() 内部需要定位底层归档时才出现。只检查 fh.closed 或打印其类型,不能证明内部所有层都仍然打开;不同回收时机也会影响暴露位置。
把读取放进同一个上下文
对于一次完整读取,让外层对象覆盖整个使用范围:
import fsspec
url = "tar://data.csv::file:///absolute/path/demo.tar"
with fsspec.open(url, mode="rb") as fh:
first = fh.read(5)
fh.seek(0)
beginning = fh.read(5)
示例路径应换成实际存在的归档,data.csv 是归档内部的成员名。不要将磁盘路径误写成成员路径,也不要为了这次读取把归档全部解压到业务目录。
所有使用 fh 的操作必须在 with 内完成。把它返回给外部调用方、放进延迟迭代器,或在上下文退出后才让另一个解析器消费数据,会再次破坏生命周期。需要返回解析结果时,在这个范围内完成解析;需要流式读取时,让拥有上下文的那一层负责整个消费过程。
例如将该成员交给 pandas,应把 pd.read_csv(fh) 放在同一个 with 内。若开启 chunksize 等延迟读取选项,块迭代也必须在退出前结束,不能只在里面创建 reader。
已有接口必须手动管理时,要同时保留外层引用,并在使用结束后关闭它:
open_file = fsspec.open(url, mode="rb")
fh = open_file.open()
try:
first = fh.read(5)
finally:
open_file.close()
不要只把原先那一行改成两个临时变量,却在返回时丢掉 open_file;跨函数的资源所有权也应由接口明确承担。
检查原解析链,而不只检查文件存在
在可控环境中保留原来的链式 URL、压缩格式和解析器设置,检查首次读取、后续读取及需要的 seek。若使用分块解析,还应走到正常结束和解析失败后的清理路径。
归档能列出成员或 exists() 返回真,都没有覆盖这里的读句柄生命周期。反过来,保存外层对象后仍失败,就应看新的关闭栈、归档内容与底层文件系统;不能把所有 seek of closed file 都归到 fsspec。
这是一项资源范围调整,不修改原归档。采用其他依赖版本时,仍需核对实际调用层是否保存了外层对象;不能用更长的休眠、强制关闭垃圾回收或忽略异常维持读取。


