基于Python正确读取资源文件

发布时间:2026/10/11 20:42:35
基于Python正确读取资源文件 前言「资源文件」指的是代码之外的静态数据配置文件、模板、词表、图标、测试用的样例数据。它们的共同特点是和代码放在一起发布程序运行时需要按名字找到它们。新手在这里踩的坑几乎一模一样本机跑得好好的换台机器或者换个目录执行就报FileNotFoundError。根因在于一个容易忽略的事实——open(data.txt)里的相对路径是相对于当前工作目录解析的不是相对于脚本文件所在目录。从项目根目录执行python tools/gen.py和先cd tools再执行同一个相对路径会指向两个不同的位置。更麻烦的是打包成 wheel 或 zip 之后包里的资源根本没有「文件系统路径」__file__推出来的路径可能不存在。本文讲清三件事相对路径为什么不可靠、怎么基于脚本或包来定位资源、以及打包分发时应该改用什么接口。示例面向 Python 3.8。一、相对路径到底相对谁Python 里open(name)会把name交给操作系统而系统是按照进程的当前工作目录来解析相对路径的# 适用于 Python 3.8import osprint(当前工作目录, os.getcwd())with open(data.txt, encodingutf-8) as f: # 找的是 cwd/data.txtprint(f.read())os.getcwd()返回的是「你敲命令时所在的目录」和脚本文件放哪儿没有必然联系。这就是为什么同一个项目有人执行成功、有人失败。可以自己验证这个差异# 适用于 Python 3.8在项目根目录执行python tools/gen.py # cwd 项目根data.txt 要放在根目录才找得到# 适用于 Python 3.8先切进子目录再执行cd tools python gen.py # cwd toolsdata.txt 要放在 tools 下才找得到脚本没变数据文件没变结果却不同——问题就出在 cwd 上。二、基于脚本自身定位要让资源始终跟着脚本走就得从脚本文件的位置出发拼路径。__file__是模块的属性值是加载该模块时使用的路径。注意它可能是相对路径所以要先abspath# 适用于 Python 3.8import osHERE os.path.dirname(os.path.abspath(__file__))with open(os.path.join(HERE, data.txt), encodingutf-8) as f:text f.read()pathlib的写法更短也是现在更推荐的风格# 适用于 Python 3.8from pathlib import PathHERE Path(__file__).resolve().parenttext (HERE / data.txt).read_text(encodingutf-8)这里用resolve()把__file__转成绝对路径并解析符号链接parent取所在目录。Path.read_text(encoding..., errors..., newline...)是pathlib提供的便捷方法内部就是「打开—读取—关闭」三步# 适用于 Python 3.8from pathlib import PathHERE Path(__file__).resolve().parent(HERE / cache).mkdir(exist_okTrue) # 配套的写资源目录(HERE / out.txt).write_text(结果, encodingutf-8)需要提醒一点write_text的默认编码同样是平台相关的写非 ASCII 内容时必须显式传encoding否则在中文 Windows 上写出来的文件在别的机器上可能读不了。三、打包分发改用 importlib.resources上面的写法在「源码目录里直接跑」时没问题但一旦把包打成 wheel 装进 site-packages或者打成 zip 分发包内资源的真实文件路径可能不存在比如被压进 zipPath(__file__).parent / data.txt就会失败。Python 3.9 起标准做法是importlib.resources# 适用于 Python 3.9from importlib import resourcestext resources.files(myapp.data).joinpath(words.txt).read_text(encodingutf-8)resources.files(anchor)返回一个「可遍历对象」Traversable它的read_text()、read_bytes()、iterdir()等方法在 zip 内也能工作。3.12 起这个参数正式改名为anchor并且可以省略省略时用调用方所在的模块。当某个第三方库只接受真实的文件系统路径比如要读st_size、或者要交给别的进程用as_file()把它解出来# 适用于 Python 3.9from importlib import resourceswith resources.as_file(resources.files(myapp.data) / logo.png) as real:print(real) # 一个 pathlib.Path指向真实的临时文件# 退出 with 后从 zip 解出来的临时文件会被清理as_file()就是为这种场景设计的返回的是上下文管理器退出时清理临时文件。旧的函数式接口resources.read_text(anchor, *path_names, encodingutf-8, errorsstrict)仍然可用但官方更推荐files().joinpath().read_text()这条链。实战一个既能源码运行、又能打包发布的资源读取函数# 适用于 Python 3.8from pathlib import Pathdef load_text(filename, encodingutf-8):优先按包资源读取不在包内时退回脚本同级目录。try:from importlib import resources # 3.9 才有完整的 files()return resources.files(__package__ or __main__).joinpath(filename).read_text(encodingencoding)except Exception:here Path(__file__).resolve().parentreturn (here / filename).read_text(encodingencoding)if __name__ __main__:print(load_text(data.txt)[:50])这个函数先把「按包资源」的路走一遍失败再退回「脚本同级目录」。真实项目里更推荐明确二选一源码内运行时用Path(__file__)打包分发时用resources.files()不要靠宽泛的except Exception掩盖配置错误——上面这样写只是为了演示两条路径的差别。常见坑点1. 用相对路径读资源❌open(config.json)换个目录执行就找不到。 ✅Path(__file__).resolve().parent / config.json。2.__file__没转绝对路径❌os.path.dirname(__file__)在__file__为相对路径时会受 cwd 影响。 ✅ 先os.path.abspath(__file__)或Path(__file__).resolve()。3. 在交互式环境里用__file__❌ 直接在 REPL 里写Path(__file__)抛NameError__file__只对从文件加载的模块存在。 ✅ 交互式环境下手写路径或用Path.cwd()。4. 打包后仍用文件路径❌ 装成 wheel 后用Path(__file__).parent / data.txt读包内资源zip 场景下路径不存在。 ✅ 改用importlib.resources.files(pkg).joinpath(...).read_text()。5.read_text()不带 encoding❌Path(a.txt).read_text()用的是平台默认编码跨平台读到乱码。 ✅Path(a.txt).read_text(encodingutf-8)。6. 拿到资源路径后当着永久路径用❌with resources.as_file(...) as p:块外继续使用p临时文件已被清理。 ✅ 所有需要真实路径的操作都放进with块内完成。7. 资源目录不在包数据里❌ 打包时只配置了.py文件words.txt没被收进 wheel。 ✅ 在打包配置里显式声明包数据或改用能声明资源的方式组织目录。8. 用os.chdir去「修正」路径❌ 程序里到处os.chdir切目录多线程下互相干扰路径彻底失控。 ✅ 不要依赖 cwd一律用基于__file__或包资源的绝对路径。总结场景推荐写法说明读脚本同级文件Path(__file__).resolve().parent / name与 cwd 无关读包内资源resources.files(pkg).joinpath(name).read_text()3.9zip 内可用需要真实路径with resources.as_file(obj) as p:退出即清理写文件Path.write_text(text, encodingutf-8)编码要显式写交互式环境手写路径或Path.cwd()没有__file__资源读取的所有麻烦都来自「路径是相对的」和「打包后没有真实文件」这两件事。前者用__file__起头解决后者用importlib.resources解决。把这两条固定成习惯脚本就再也不会因为「在哪儿执行」而时好时坏了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询