在Python中以编程方式确定distutils数据文件的位置

14 投票
3 回答
7524 浏览
提问于 2025-04-17 08:59

我正在尝试在我的包中使用distutils包含数据文件,并通过相对路径来引用它们(参考 http://docs.python.org/distutils/setupscript.html#distutils-additional-files

我的目录结构是:

myproject/
  mycode.py
  data/
    file1.dat

mycode.py 中有代码,这实际上是包中的一个脚本。它需要访问 data/file1.dat,并通过这个相对路径来引用它。在 setup.py 中,我有:

setup(
 ...
 scripts = "myproject/mycode.py"
 data_files = [('data', 'myproject/data/file1.dat')]
)

假设用户现在使用:

python setup.py --prefix=/home/user/

那么 mycode.py 会出现在类似 /home/user/bin/ 的地方。但是对 data/file1.dat 的引用现在就失效了,因为脚本和数据文件不在同一个地方。

我该如何从 mycode.py 中找到 myproject/data/file1.dat 的绝对路径,以便根据用户安装包的位置正确引用它呢?

编辑
当我用 prefix=/home/user/ 安装时,我在 /home/user/ 中得到了 data/file1.dat,这正是我想要的,唯一缺少的就是如何通过编程方式获取这个文件的绝对路径,只知道相对路径而不知道用户安装包的位置。当我尝试使用 package_data 而不是 data_files 时,它不起作用——我根本没有在任何地方创建 data/file1.dat,即使我删除了 MANIFEST 文件。

我已经阅读了所有关于这个显然非常常见问题的讨论。然而,所有提出的解决方案都没有解决我上面提到的情况,即需要访问 data_files 的代码是一个脚本,而且它的位置可能会根据 setup.py--prefix 参数而变化。我能想到的唯一解决办法就是把数据文件添加到 setup() 中的 scripts=,像这样:

setup(
  ...
  scripts = ["myproject/mycode.py", "myproject/data/file1.data"]
)

这真是个糟糕的解决办法,但这是我能想到的确保 file1.datascripts= 中定义的脚本在同一个地方的唯一方法,因为我找不到任何平台无关且对安装敏感的API来获取用户运行 setup.py installdata_files 的位置(可能带有 --prefix= 参数)。

3 个回答

0

如果你想在Windows或Linux的virtualenv环境里或者外面都能顺利运行,可以先导入pipos这两个模块,然后执行以下代码:

os.path.split(os.path.split(pip.__file__)[0])[0]

完整示例

from setuptools import setup, find_packages
from os import path
from functools import partial
from pip import __file__ as pip_loc


if __name__ == '__main__':
    package_name = 'gen'

    templates_join = partial(path.join, path.dirname(__file__),
                             package_name, 'templates')
    install_to = path.join(path.split(path.split(pip_loc)[0])[0],
                           package_name, 'templates')

    setup(
        name=package_name,
        version='0.0.1',
        test_suite=package_name + '.tests',
        packages=find_packages(),
        package_dir={package_name: package_name},
        data_files=[(install_to, [templates_join('.gitignore'),
                                  templates_join('logging.conf')])]
    )

参考链接(我自己的): https://stackoverflow.com/a/29120636

9

你可以使用 pkg_resources.resource_filename 这个工具来获取你在 package_data 中某个文件的文件名。

14

我觉得大家的困惑主要来自于脚本的使用。脚本应该是指可以运行的程序,也许是和你的包相关的工具脚本,或者是进入你包功能的入口点。无论是哪种情况,你都应该预期这些脚本不会和你包的其他部分一起安装。这种预期主要是因为通常情况下,包被视为库(安装在lib目录下),而脚本则被视为可执行文件(安装在bin或Scripts目录下)。此外,数据文件既不是可执行文件也不是库,它们是完全独立的。

所以在脚本中,你需要确定数据文件的位置。根据Python文档

如果目录是相对路径,它会相对于安装前缀进行解释。

因此,你应该在mycode脚本中写类似下面的代码来找到数据文件:

import sys
import os

def my_func():
    with open(os.path.join(sys.prefix, 'data', 'file1.dat')) as f:
        print(next(f))

if __name__ == '__main__':
    my_func()

如果你对代码和数据没有打包在一起的方式不满意(我也会不满意),那么我建议你重新组织你的包,使其成为一个真正的Python包(和模块),并使用packages=和package_data=将数据注入到包中,然后创建一个简单的脚本来调用包中的模块。

我通过创建这样的结构来实现:

.
│   setup.py
│
├───myproject
│   │   mycode.py
│   │   __init__.py
│   │
│   └───data
│           file1.dat
│
└───scripts
        run-my-code.py

配合setup.py:

from distutils.core import setup

setup(
    name='myproject',
    version='1.0',
    scripts=['scripts/run-my-code.py'],
    packages=['myproject'],
    package_data = {
        'myproject': ['data/file1.dat'],
    },
)

run-my-code.py 只是:

from myproject import mycode

mycode.my_func()

__init__ 是空的,而mycode.py看起来像:

import os

here = os.path.dirname(__file__)

def my_func():
    with open(os.path.join(here, 'data', 'file1.dat')) as f:
        print(next(f))

这种方法将数据和代码打包在一起(在site-packages/myproject中),并且只在不同的位置安装脚本(这样它就能出现在$PATH中)。

撰写回答