优先用package_data处理包内资源(如myproject/utils/data/且含__init__.py),非包路径资源需MANIFEST.in配合include_package_data=True;读取时须用importlib.resources而非普通open。

setup.py里用package_data还是include_package_data?
关键看资源文件是否和Python模块在同一个包目录下。如果资源文件放在myproject/utils/data/这种路径,且myproject/utils/__init__.py存在,那属于“包内资源”,优先用package_data;如果资源散落在项目根目录或非包路径(比如docs/、scripts/),就得靠MANIFEST.in配合include_package_data=True。
常见错误是只写package_data却漏掉__init__.py——没有它,setuptools根本不认为那是“包”,资源直接被忽略。
-
package_data只作用于已声明的packages列表里的包 - 通配符如
"*.json"不会递归匹配子目录,要写成"data/*.json"或"data/**/*.json"(需Python 3.5+且setuptools ≥ 40.8.0) - 路径分隔符一律用
/,即使在Windows上
pyproject.toml中怎么声明非py文件?
现代方式推荐用pyproject.toml替代setup.py,但资源打包逻辑没变:仍靠package-data字段(对应旧版package_data)或include字段(对应MANIFEST.in语义)。
示例片段:
立即学习“Python免费学习笔记(深入)”;
[project] packages = ["myproject"] [project.package-data] "myproject" = ["data/*.csv", "templates/*.jinja2"]
注意:package-data值必须是字符串列表,不能写成嵌套字典或用glob递归语法(如**/*.txt不被标准PEP 517构建器支持)。
- 若用
build-system.requires = ["setuptools>=61.0"],可启用include字段走MANIFEST.in兼容路径 -
include字段支持recursive-include语法,但仅限源码分发(sdist),wheel里不会自动包含未声明的包外文件
打包后资源文件找不到的典型原因
运行时抛FileNotFoundError或importlib.resources.files()返回空,大概率是路径加载方式错了。Python包内资源不能用普通文件路径打开,必须通过importlib.resources(Python 3.9+)或pkg_resources(旧版)。
错误示范:open("myproject/data/config.yaml") —— 这依赖当前工作目录,wheel安装后路径根本不存在。
- 正确读取方式(3.9+):
importlib.resources.files("myproject").joinpath("data/config.yaml").read_text() - 兼容旧版(需
pkg_resources):pkg_resources.resource_string("myproject", "data/config.yaml") - 用
files().joinpath()前先确认模块名拼写和大小写——包名区分大小写,且不能含-(会被转成_)
测试打包结果最简单的验证方法
别等用户报错,本地立刻验证wheel内容是否真包含了你要的文件:
python -m build unzip -l dist/*.whl | grep -E "\.(json|csv|jinja2)$"
再装到临时环境里实测加载逻辑:
python -m venv .testenv
.testenv/bin/pip install dist/*.whl
.testenv/bin/python -c "from importlib import resources; print(list(resources.files('myproject').joinpath('data').iterdir()))"
容易被忽略的是:CI/CD流水线里build命令默认只生成sdist,wheel需要显式加--wheel参数,否则测试的其实是源码包而非最终分发产物。


















