【问题标题】:Determining the location of distutils data files programmatically in Python在 Python 中以编程方式确定 distutils 数据文件的位置
【发布时间】:2012-01-27 13:08:52
【问题描述】:

我正在尝试在我的包的 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/ 安装它时,我得到data/file1.dat/home/user/ 中创建,这正是我想要的,唯一缺少的部分是如何以编程方式检索该文件的绝对路径,只给定一个相对路径而不知道用户安装软件包的位置。当我尝试使用package_data 而不是data_files 时,它不起作用 - 我根本无法在任何地方创建data/file1.dat,即使我删除了我的MANIFEST 文件。

我已经阅读了当前关于这个显然非常常见的问题的所有讨论。然而,所有建议的解决方案都没有处理我上面的情况,需要访问data_files 的代码是一个脚本,它的位置可能会根据@ 的--prefix 参数而改变987654342@。我能想到的解决这个问题的唯一方法是将数据文件添加到setup() 中的scripts=,如下所示:

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

这是一个可怕的 hack,但这是我能想到的唯一方法,以确保 file1.datascripts= 中定义的脚本位于同一位置,因为我找不到任何独立于平台且对安装敏感的 API在用户运行setup.py install(可能使用--prefix= args)后恢复data_files 的位置。

【问题讨论】:

  • 尝试将 mycode.__file__ 魔法变量插入每个模块?
  • @MikkoOhtamaa: __ file __ 不会给你安装模块的位置,它可以给你正在执行的文件的位置(所以如果你 setup.py 在 ~/ lib/site-packages/yourproject/ 你不一定会得到那个路径)
  • 那不是真的。 module.__file__ 为您提供模块的加载位置,也就是它的安装位置?
  • @MikkoOhtamaa:你说得对,我收回我的声明
  • @MikkoOhtamaa 找出mycode 模块的位置如何帮助回答提出的问题是找出数据文件的位置?

标签: python packaging setuptools distutils python-module


【解决方案1】:

您应该可以使用pkg_resources.resource_filename 来获取您的 package_data 中文件的文件名。

【讨论】:

  • 是的,这是要走的路。就像疲惫的印第安纳琼斯遇到了一个要与之战斗的人,因为他手头有枪,所以他只是向他开枪。
  • 这不适用于通过data_files 安装的文件。它适用于通过package_data 安装的文件。
【解决方案2】:

对于一个可以在 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

【讨论】:

    【解决方案3】:

    我认为混淆是由于脚本的使用引起的。脚本应该引用一个可运行的可执行文件,可能是与您的包相关的实用程序脚本,或者可能是您的包功能的入口点。在任何一种情况下,您都应该期望任何脚本都不会与包的其余部分一起安装。这种期望主要是由于将包视为库(并安装到 lib 目录)而脚本被视为可执行文件(并安装到 bin 或 Scripts 目录)的约定。此外,数据文件既不是可执行文件也不是库,并且是完全独立的。

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

    如果目录是一个相对路径,它被解释为相对于 安装前缀。

    因此,您应该在 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 包(和模块)并使用包= 和 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 中)。

    【讨论】:

    • 我会将 os.path.join(here, 'data/file1.dat') 更改为 os.path.join(here, 'data', 'file1.dat'),但这几乎只是风格问题,因为 os.path 会自动管理斜杠。
    • 您可以通过--install-data=<path> 覆盖数据文件的位置,这将破坏这种方法。因此,我认为您根本不应该依赖 sys.prefix。
    猜你喜欢
    • 2012-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    • 2010-12-04
    • 1970-01-01
    相关资源
    最近更新 更多