Программное определение местоположения файлов данных distutils в Python

Я пытаюсь включить файлы данных в 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 на setup.py. Единственный хак, который я могу придумать, чтобы решить эту проблему, - это добавить файл данных в scripts= в setup(), например:

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

это ужасный взлом, но это единственный способ, который я могу придумать, чтобы гарантировать, что file1.data будет в том же месте, что и сценарии, определенные в scripts=, поскольку я не могу найти какой-либо независимый от платформы и чувствительный к установке API для восстановления местоположения data_files после пользователь запустил setup.py install (возможно, с --prefix= аргументами).


person Community    schedule 25.12.2011    source источник
comment
Попробуйте вставить волшебную переменную mycode.__file__ в каждый модуль?   -  person Mikko Ohtamaa    schedule 25.12.2011
comment
@MikkoOhtamaa: __ файл __ не даст вам расположение установленного модуля, он может дать вам расположение исполняемого файла (поэтому, если вы setup.py устанавливаете что-то в ~/lib/site-packages/yourproject/ вы не обязательно получите этот путь обратно)   -  person    schedule 25.12.2011
comment
Это не правда. module.__file__ дает вам расположение модуля, откуда он загружается, и это место, где он установлен?   -  person Mikko Ohtamaa    schedule 26.12.2011
comment
@MikkoOhtamaa: ты прав, я отказываюсь от своего заявления   -  person    schedule 03.01.2012
comment
@MikkoOhtamaa Как определение местоположения модуля mycode помогает ответить на заданный вопрос, который заключается в том, чтобы узнать местоположение файла данных?   -  person Piotr Dobrogost    schedule 20.10.2012
comment
Вы решили эту проблему и если да, то как?   -  person Piotr Dobrogost    schedule 20.10.2012


Ответы (3)


Я думаю, что путаница возникает из-за использования скриптов. Сценарии должны относиться к исполняемым исполняемым файлам, возможно, к служебному сценарию, связанному с вашим пакетом, или, возможно, к точке входа в функциональность вашего пакета. В любом случае вы должны ожидать, что никакие сценарии не будут установлены вместе с остальной частью вашего пакета. Это ожидание связано главным образом с соглашением, согласно которому пакеты считаются библиотеками (и устанавливаются в каталоги 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).

person Jason R. Coombs    schedule 10.02.2012
comment
Я бы изменил os.path.join(here, 'data/file1.dat') на os.path.join(here, 'data', 'file1.dat'), но это почти вопрос стиля, поскольку os.path управляет косыми чертами автоматически. - person LRMAAX; 18.12.2013
comment
Вы можете перезаписать расположение файлов данных через --install-data=<path>, что просто нарушит этот подход. Таким образом, я не думаю, что вы вообще должны полагаться на sys.prefix. - person hasufell; 26.02.2016

Вы должны иметь возможность использовать pkg_resources.resource_filename, чтобы получить имя файла файл в вашем package_data.

person Jens Timmerman    schedule 03.06.2013
comment
Да, это путь. Это похоже на то, как усталый Индиана Джонс встречает парня, с которым хочет подраться, и, поскольку у него есть пистолет, он просто стреляет в него. - person Jan Vlcinsky; 19.05.2014
comment
Это не работает с файлом, установленным через data_files. Работает с файлами, установленными через package_data. - person Bruno Bronosky; 12.03.2015

Для решения, которое будет хорошо работать внутри/снаружи virtualenv в Windows/Linux, импортируйте 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

person A T    schedule 19.03.2015