在Python中以编程方式确定distutils数据文件的位置
我正在尝试在我的包中使用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.data
和 scripts=
中定义的脚本在同一个地方的唯一方法,因为我找不到任何平台无关且对安装敏感的API来获取用户运行 setup.py install
后 data_files
的位置(可能带有 --prefix=
参数)。
3 个回答
如果你想在Windows或Linux的virtualenv
环境里或者外面都能顺利运行,可以先导入pip
和os
这两个模块,然后执行以下代码:
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
你可以使用 pkg_resources.resource_filename 这个工具来获取你在 package_data 中某个文件的文件名。
我觉得大家的困惑主要来自于脚本的使用。脚本应该是指可以运行的程序,也许是和你的包相关的工具脚本,或者是进入你包功能的入口点。无论是哪种情况,你都应该预期这些脚本不会和你包的其他部分一起安装。这种预期主要是因为通常情况下,包被视为库(安装在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中)。