如何用Python setup.py打包非代码数据并解决路径问题
Python项目资源文件打包与访问问题
项目结构
my-project venv tests setup.py abc __init__.py my_pckg __init__.py foo.py static data_0.txt sub_dir1 data_1.yaml sub_dir2 data_2.csv another file with space in name.pdf
当前foo.py代码
# abc/my_pckg/foo.py import os import yaml with open(os.path.join("abc", "static", "sub_dir1", "data_1.yaml"), "r") as f: content = yaml.safe_load(f) if __name__ == "__main__": print(content)
运行现状
- 在
my-project目录打开Python控制台执行from abc.my_pckg.foo import content,正常运行; - 执行
python abc/my_pckg/foo.py,正常运行; - 打包成whl并在新环境安装后,运行报错找不到
abc\static\data_1.yaml,安装后静态文件实际位于venv/Lib/site_packages/static。
疑问与需求
- 如何修正配置与代码,让三种运行方式都正常工作?
- 如何处理带空格的文件名
another file with space in name.pdf? - 是否应该用
pkg_resources或importlib?具体怎么操作?
解答
1. 让三种运行方式正常工作的修正方案
打包配置修正(setup.py)
问题核心是打包时静态文件未归属到abc包目录,而是直接放到了site-packages根目录。修改setup.py,通过package_data指定静态文件归属:
from setuptools import setup, find_packages setup( name="abc", version="0.1.0", packages=find_packages(), # 指定abc包下的静态文件 package_data={ "abc": [ "static/*.txt", "static/sub_dir1/*.yaml", "static/sub_dir2/*.csv", "static/sub_dir2/*.pdf" ] }, # 可选:配合MANIFEST.in自动包含文件 include_package_data=True, )
如果不想逐个列举文件,用递归通配符更高效:
package_data={ "abc": ["static/**/*"], },
同时创建MANIFEST.in文件(与setup.py同目录),确保打包时包含静态文件:
recursive-include abc/static *
代码读取逻辑修正
硬编码相对路径仅在开发环境有效,打包后包路径会变化,必须通过包元信息动态获取资源路径:
方案A:Python 3.7+ 推荐用importlib.resources(标准库)
# abc/my_pckg/foo.py import yaml from importlib.resources import files # 定位到abc.static.sub_dir1下的data_1.yaml resource_path = files("abc.static.sub_dir1").joinpath("data_1.yaml") with open(resource_path, "r", encoding="utf-8") as f: content = yaml.safe_load(f) if __name__ == "__main__": print(content)
Python 3.9+ 可使用更简洁的read_text直接读取内容:
from importlib.resources import read_text content = yaml.safe_load(read_text("abc.static.sub_dir1", "data_1.yaml", encoding="utf-8"))
方案B:兼容旧版本用pkg_resources
若需支持Python 3.6及以下,使用pkg_resources(依赖setuptools):
# abc/my_pckg/foo.py import yaml import pkg_resources # 获取资源文件的绝对路径 resource_path = pkg_resources.resource_filename( "abc", "static/sub_dir1/data_1.yaml" ) with open(resource_path, "r", encoding="utf-8") as f: content = yaml.safe_load(f) if __name__ == "__main__": print(content)
修改后,三种运行场景都会正确定位资源:开发环境读取项目内的abc/static,安装后读取site-packages中abc包下的static目录。
2. 带空格的文件名处理
带空格的文件名无需额外处理,只要打包配置正确匹配,读取时用上述importlib或pkg_resources方法即可。例如读取目标PDF文件:
# importlib写法 pdf_path = files("abc.static.sub_dir2").joinpath("another file with space in name.pdf") # pkg_resources写法 pdf_path = pkg_resources.resource_filename( "abc", "static/sub_dir2/another file with space in name.pdf" )
Python的文件操作会自动处理路径中的空格,无需手动转义。
3. pkg_resources vs importlib.resources选择
- 优先用
importlib.resources:它是Python标准库的一部分(3.7+),不依赖第三方包,API简洁现代,性能更优。 pkg_resources:属于setuptools组件,兼容性更强(支持Python 2和3.6以下),但会加载整个包的元数据,性能略差。
具体操作参考上述代码示例,根据项目Python版本要求选择即可。
内容的提问来源于stack exchange,提问作者Amin Ba
相关产品推荐
相关产品推荐

