setuptools:包数据文件夹位置

109 投票
4 回答
61525 浏览
提问于 2025-04-16 08:57

我用setuptools来发布我的Python包。现在我需要发布一些额外的数据文件。

根据我从setuptools的文档中了解到的,我需要把数据文件放在包的目录里。不过,我更希望把这些数据文件放在根目录下的一个子目录里。

我想避免的情况是:

/ #root
|- src/
|  |- mypackage/
|  |  |- data/
|  |  |  |- resource1
|  |  |  |- [...]
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

我希望得到的情况是:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

我觉得如果没有必要的话,放这么多子目录让我不太舒服。我找不到为什么我必须把文件放在包目录里的理由。对我来说,处理这么多嵌套的子目录也很麻烦。难道有什么好的理由能解释这个限制吗?

4 个回答

0

我可以使用 importlib_resources 或者 importlib.resources(这取决于你使用的Python版本)。

https://importlib-resources.readthedocs.io/en/latest/using.html

21

我想我找到了一种不错的折中方案,这样你就可以保持以下的结构:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

你应该把数据安装为 package_data,这样可以避免在 samplebias 的回答中提到的问题。不过,为了保持文件结构,你需要在你的 setup.py 文件中添加:

try:
    os.symlink('../../data', 'src/mypackage/data')
    setup(
        ...
        package_data = {'mypackage': ['data/*']}
        ...
    )
finally:
    os.unlink('src/mypackage/data')

这样我们就能“及时”创建合适的结构,同时保持我们的源代码树井然有序。

要在代码中访问这些数据文件,你只需使用:

data = resource_filename(Requirement.parse("main_package"), 'mypackage/data')

我还是不太喜欢在代码中指定 'mypackage',因为这些数据可能和这个模块没有直接关系,但我想这也是个不错的折中方案。

124

选项 1:作为包数据安装

把数据文件放在你的 Python 包的根目录下的主要好处是,你不需要担心这些文件在用户的系统上会放在哪里。用户可能使用的是 Windows、Mac、Linux、某些移动平台,或者是在一个 Egg 包里。无论在哪里安装,你总能相对你的 Python 包根目录找到 data 这个文件夹。

比如,如果我的项目结构是这样的:

project/
    foo/
        __init__.py
        data/
            resource1/
                foo.txt

你可以在 __init__.py 文件里添加一个函数,用来找到数据文件的绝对路径:

import os

_ROOT = os.path.abspath(os.path.dirname(__file__))
def get_data(path):
    return os.path.join(_ROOT, 'data', path)

print get_data('resource1/foo.txt')

输出结果:

/Users/pat/project/foo/data/resource1/foo.txt

当项目作为 Egg 安装后,data 的路径会改变,但代码不需要修改:

/Users/pat/virtenv/foo/lib/python2.6/site-packages/foo-0.0.0-py2.6.egg/foo/data/resource1/foo.txt

选项 2:安装到固定位置

  1. 通过配置文件、命令行参数来传入 data 的位置,或者
  2. 把位置嵌入到你的 Python 代码里。

如果你打算分发你的项目,这种方法就不太理想。如果你真的想这么做,你可以通过传入一个包含文件组的目标位置的元组列表,来把 data 安装到你想要的地方:

from setuptools import setup
setup(
    ...
    data_files=[
        ('/var/data1', ['data/foo.txt']),
        ('/var/data2', ['data/bar.txt'])
        ]
    )

更新:一个递归查找 Python 文件的 shell 函数示例:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}

撰写回答