Доступ до даних у підкаталозі пакета


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()

44
Це не працюватиме, якщо файли знаходяться в дистрибутиві (IE. Яйце). Використовуйте pkg_resources, щоб перейти до файлу даних.
Кріс

2
Дійсно, це порушено.
Федеріко

1
Також __file__не працює з py2exe, оскільки значенням буде шлях до zip-файлу.
Под

1
Це насправді спрацювало для мене. Не виникло жодних проблем. Я використовую python 3.6
Хорхе

1
Це не спрацює у випадку розповсюдження (яйце тощо).
Адарш

166

Стандартний спосіб зробити це з пакетами 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')

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

@gerrit 2-й містить текст. "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')
)

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


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

@ankostis setuptools досить розумний, щоб витягнути архів, якщо він виявить, що ви __file__десь використовувались . У моєму випадку я використовую бібліотеку, яка дуже хоче шляхи, а не потоки. Звичайно, я міг записати файли тимчасово на диск, але, будучи ледачим, я просто використовую функцію setuptools.
letmaik
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.