manifest.in仅对sdist生效,不影响wheel;pip install .默认优先用wheel,故静态文件缺失,须配合setup.py中package_data和include_package_data=true双配置。

MANIFEST.in 为什么对 setup.py 不起作用
因为 setup.py 默认只打包 py 文件,MANIFEST.in 控制的是源码分发包(sdist)中包含哪些非 Python 文件,和 wheel(bdist_wheel)基本无关。如果你用 pip install . 本地安装却没看到静态文件,大概率是 pip 从 wheel 安装的——而 wheel 根本不读 MANIFEST.in。
常见错误现象:python setup.py sdist 打出的 tar.gz 里有文件,但 pip install . 后 import mypkg; mypkg.get_data("data/config.json") 报错找不到路径。
-
MANIFEST.in只影响sdist,不影响bdist_wheel - wheel 中要包含非代码文件,必须显式声明在
setup.py的package_data或data_files里 - 如果项目结构是
mypkg/下带data/子目录,别忘了在mypkg/__init__.py中设__package_data__ = True(非必需,但某些旧工具链会检查)
正确写法:MANIFEST.in + package_data 双保险
假设目录结构如下:
myproject/
├── setup.py
├── MANIFEST.in
└── mypkg/
├── __init__.py
├── core.py
└── data/
├── config.json
└── templates/
└── email.txt
你需要两处同时配置:
-
MANIFEST.in写:recursive-include mypkg/data *.json *.txt
-
setup.py中加:setup( # ... package_data={ "mypkg": ["data/*.json", "data/templates/*.txt"], }, include_package_data=True, # 这行让 setuptools 合并 MANIFEST.in 规则进 sdist )
注意:package_data 的键是包名(字符串),值是相对于该包根目录的 glob 模式;路径分隔符一律用 /,即使在 Windows 上。
读取这些文件时别硬编码路径
打包后文件不再在原始文件系统路径下,不能用 os.path.join(os.path.dirname(__file__), "data/config.json")——wheel 解压后可能被重定位或内存加载。
推荐用 importlib.resources(Python 3.7+)或兼容方案:
- Python 3.9+:
from importlib import resources; content = resources.files("mypkg").joinpath("data/config.json").read_text() - Python 3.7–3.8:
from importlib import resources; content = resources.read_text("mypkg", "data/config.json") - 需兼容 3.6 或更老版本?改用
pkg_resources.resource_string("mypkg", "data/config.json"),但注意pkg_resources已被标记为 legacy
错误示范:open("mypkg/data/config.json") —— 这依赖当前工作目录,不是包内路径。
调试技巧:验证文件是否真打进去了
别猜,直接看生成物:
- 检查 sdist:
tar -tzf dist/mypkg-1.0.tar.gz | grep data/ - 检查 wheel:
unzip -l dist/mypkg-1.0-py3-none-any.whl | grep data/ - 安装后验证:
python -c "import mypkg; print(mypkg.__file__)"; ls $(dirname $(python -c "import mypkg; print(mypkg.__file__)"))/data/
如果 unzip -l 看不到 data/,说明 package_data 配置有误或 glob 模式没匹配上;如果 tar -tzf 能看到但 wheel 里没有,说明漏了 package_data 声明。
容易被忽略的一点:package_data 只处理「已声明为 package」的目录(即含 __init__.py),mypkg/data/ 如果没有 __init__.py,哪怕写了 recursive-include,也不会进 wheel——除非你把它也变成一个子包,或者改用 data_files(但那会装到全局路径,不推荐)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











