Доступ к данным в подкаталоге пакета

130

Я пишу пакет python с модулями, которым необходимо открывать файлы данных в ./data/подкаталоге. Прямо сейчас у меня есть пути к файлам, жестко закодированные в мои классы и функции. Я хотел бы написать более надежный код, который может получить доступ к подкаталогу независимо от того, где он установлен в системе пользователя.

Я пробовал разные методы, но пока безуспешно. Кажется, что большинство команд «текущего каталога» возвращают каталог системного интерпретатора python, а не каталог модуля.

Похоже, это должна быть обычная тривиальная проблема. Но я не могу понять этого. Частично проблема в том, что мои файлы данных не являются .pyфайлами, поэтому я не могу использовать функции импорта и тому подобное.

Какие-либо предложения?

Сейчас мой каталог пакетов выглядит так:

/
__init__.py
module1.py
module2.py
data/   
   data.txt

Я пытаюсь получить доступ data.txtиз module*.py!

Джейкоб Лайлс
источник

Ответы:

24

Вы можете использовать, __file__чтобы получить путь к пакету, например:

import os
this_dir, this_filename = os.path.split(__file__)
DATA_PATH = os.path.join(this_dir, "data", "data.txt")
print open(DATA_PATH).read()
RichieHindle
источник
44
Это не сработает, если файлы находятся в дистрибутиве (IE. Egg). Используйте pkg_resources, чтобы получить файл данных.
Крис
2
Действительно, это не работает.
Федерико
1
Также __file__не работает с py2exe, так как значением будет путь к zip-файлу.
Pod
1
Это действительно сработало для меня. Проблем не было. Я использую python 3.6
Хорхе
1
Это не сработает в случае распространения (яйцо и т. Д.).
Адарш Триведи
167

Стандартный способ сделать это - использовать пакеты setuptools и pkg_resources.

Вы можете разместить свой пакет в соответствии со следующей иерархией и настроить файл установки пакета так, чтобы он указывал на ваши ресурсы данных, согласно этой ссылке:

http://docs.python.org/distutils/setupscript.html#installing-package-data

Затем вы можете повторно найти и использовать эти файлы с помощью pkg_resources по этой ссылке:

http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access

import pkg_resources

DATA_PATH = pkg_resources.resource_filename('<package name>', 'data/')
DB_FILE = pkg_resources.resource_filename('<package name>', 'data/sqlite.db')
elliot42
источник
7
Не создаст ли pkg_resources зависимость времени выполнения от setuptools ? Например, я распространяю пакет Debian, так почему я должен зависеть python-setuptoolsтолько в этом? Пока __file__у меня все работает нормально.
mlt
4
Почему это лучше: класс ResourceManager обеспечивает единый доступ к ресурсам пакета, независимо от того, существуют ли эти ресурсы в виде файлов и каталогов или сжаты в каком-либо архиве
vrdhn
4
Отличное предложение, спасибо. Я реализовал стандартный файл для открытия с помощьюfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst
5
Как это будет работать при использовании пакета, если он не установлен? Я имею в виду, что просто тестирую локально
Клаудиу
11
В python 3.7 importlib.resourcesзаменяет pkg_resourcesдля этой цели (из-за проблем с производительностью).
Benjimin
13

Чтобы предоставить решение, работающее сегодня. Определенно используйте этот API, чтобы не изобретать все эти колеса.

Требуется настоящее имя файла файловой системы. Сжатые яйца будут извлечены в каталог кеша:

from pkg_resources import resource_filename, Requirement

path_to_vik_logo = resource_filename(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Вернуть читаемый объект в виде файла для указанного ресурса; это может быть реальный файл, StringIO или другой подобный объект. Поток находится в «двоичном режиме» в том смысле, что любые байты, находящиеся в ресурсе, будут читаться как есть.

from pkg_resources import resource_stream, Requirement

vik_logo_as_stream = resource_stream(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Обнаружение пакетов и доступ к ресурсам с помощью pkg_resources

Саша Готфрид
источник
10

Часто нет смысла давать ответ, в котором подробно описывается код, который не работает как есть, но я считаю, что это исключение. Добавлен Python 3.7, importlib.resourcesкоторый должен заменить pkg_resources. Это будет работать для доступа к файлам внутри пакетов, в именах которых нет косой черты , т.е.

foo/
    __init__.py
    module1.py
    module2.py
    data/   
       data.txt
    data2.txt

т.е. вы можете получить доступ к data2.txtпакету, fooнапример,

importlib.resources.open_binary('foo', 'data2.txt')

но он потерпит неудачу за исключением

>>> importlib.resources.open_binary('foo', 'data/data.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/importlib/resources.py", line 87, in open_binary
    resource = _normalize_path(resource)
  File "/usr/lib/python3.7/importlib/resources.py", line 61, in _normalize_path
    raise ValueError('{!r} must be only a file name'.format(path))
ValueError: 'data/data2.txt' must be only a file name

Это не может быть решена только путем размещения __init__.pyв dataи затем использовать его в качестве пакета:

importlib.resources.open_binary('foo.data', 'data.txt')

Причина такого поведения - «это намеренно» ; но дизайн может измениться ...

Антти Хаапала
источник
Есть ли у вас лучшая ссылка на «это задумано», чем видео на YouTube, желательно с текстом?
gerrit 03
@gerrit второй действительно содержит текст. "This was a deliberate choice, but I think you have a valid use case. @brettcannon what do you think? And if we allow this, should we make sure it gets into Python 3.7?"
Антти Хаапала
8

Вам нужно имя для всего вашего модуля, вам дано дерево каталогов, не перечисляющее эту деталь, для меня это сработало:

import pkg_resources
print(    
    pkg_resources.resource_filename(__name__, 'data/data.txt')
)

Notibly setuptools, похоже, не разрешает файлы на основе совпадения имен с файлами упакованных данных, так что вы должны включать data/префикс в значительной степени, несмотря ни на что. Вы можете использовать, os.path.join('data', 'data.txt)если вам нужны альтернативные разделители каталогов. Обычно я не вижу проблем совместимости с жестко запрограммированными разделителями каталогов в стиле unix.

ThorSummoner
источник
docs.python.org/3.6/distutils/… > Обратите внимание, что любые пути (файлы или каталоги), указанные в сценарии установки, должны быть записаны с использованием соглашения Unix, т. е. разделены косой чертой. Distutils позаботится о преобразовании этого независимого от платформы представления во все, что подходит для вашей текущей платформы, прежде чем фактически использовать имя пути. Это делает ваш установочный скрипт переносимым между операционными системами, что, конечно же, является одной из основных целей Distutils. В этом духе все пути в этом документе разделены косой чертой.
changyuheng
6

Думаю, я нашел ответ.

Я создаю модуль data_path.py, который импортирую в другие мои модули, содержащие:

data_path = os.path.join(os.path.dirname(__file__),'data')

Затем я открываю все свои файлы с помощью

open(os.path.join(data_path,'filename'), <param>)
Джейкоб Лайлс
источник
2
Это не сработает, если ресурс находится в архиве (например, в заархивированном яйце). Предпочитаю что-то подобное:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis 01
@ankostis setuptools достаточно умен, чтобы распаковать архив, если обнаружит, что вы __file__где-то использовали . В моем случае я использую библиотеку, которой действительно нужны пути, а не потоки. Конечно, я мог бы временно записать файлы на диск, но из-за лени я просто использую функцию setuptools.
letmaik