Как прочитать (статический) файл из пакета Python? - PullRequest
57 голосов
/ 17 мая 2011

Не могли бы вы сказать мне, как я могу прочитать файл, который находится внутри моего пакета Python?

Моя ситуация

Загружаемый пакет содержит несколько шаблонов (текстовые файлы, используемые в качестве строк), которые я хочу загрузить из программы. Но как мне указать путь к такому файлу?

Представьте, что я хочу прочитать файл из:

package\templates\temp_file

Какая-то манипуляция с путём? Отслеживание базового пути пакета?

Ответы [ 8 ]

106 голосов
/ 02 января 2014

TLDR; Используйте модуль importlib.resources стандартной библиотеки , как описано в методе № 2 ниже.

традиционный pkg_resources из setuptools больше не рекомендуется из-за соображений производительности .
Сначала я сохранил традиционное перечисленное, чтобы объяснить различия с новым методом при переносе существующего кода (портирование также объяснено здесь ).


Предположим, что ваши шаблоны находятся в папке, вложенной в пакет вашего модуля:

  <your-package>
    +--<module-asking-the-file>
    +--templates/
          +--temp_file                         <-- We want this file.

Примечание 1: Конечно, мы НЕ должны возиться с атрибутом __file__ (например, код будет поврежден при подаче из почтового индекса).

Примечание 2: Если высобирая этот пакет, не забудьте объявить ваши файлы данных как package_data или data_files в вашем setup.py.

1) Используя pkg_resources из setuptools (медленно)

Вы можете использовать пакет pkg_resources из setuptools дистрибутива, но , который идет со стоимостью, производительность :

import pkg_resources

# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file'))  # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)

Советы:

  • Это будет считывать данные, даже есливаш дистрибутив заархивирован, так что вы можете установить zip_safe=True в вашем setup.py и / или использовать долгожданный zipapp упаковщик из python-3.5 для создания собственногодистрибутивы.

  • Не забудьте добавить setuptools в ваши требования времени выполнения (например, в install_requires`).

... и обратите внимание, что в соответствии с документацией Setuptools / pkg_resources вы не должны использовать os.path.join:

Базовый доступ к ресурсам

Обратите внимание, что ресурсимена должны быть / -разделенными путями и не могут быть абсолютными (то есть без начального /) или содержать относительные имена, такие как "..". не используйте os.path подпрограммы для манипулирования путями ресурсов, поскольку они являются не путями файловой системы.

2) Python> = 3.7, или используйтеbackported importlib_resources библиотека

Используйте модуль importlib.resources стандартной библиотеки , который более эффективен, чем setuptools, выше:

try:
    import importlib.resources as pkg_resources
except ImportError:
    # Try backported to PY<37 `importlib_resources`.
    import importlib_resources as pkg_resources

from . import templates  # relative-import the *package* containing the templates

template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')

Внимание:

Относительно функции read_text(package, resource):

  • package может быть либо строкой, либо модулем.
  • resource больше не является путем, а просто именем файла ресурса, который нужно открыть в существующем пакете;он может не содержать разделителей пути и не может иметь подресурсов (то есть он не может быть каталогом).

Для примера, заданного в вопросе, мы должны теперь:

  • превратить <your_package>/templates/ в правильный пакет, создав в нем пустой файл __init__.py,
  • , поэтому теперь мы можем использовать простое (возможно относительное) выражение import (нетбольше парсинга имен пакетов / модулей),
  • и просто запросите resource_name = "temp_file" (без пути).

Советы:

  • Все становится интересным, когда запрашивается фактическое имя файла с path(), так как теперь контекстные менеджеры используются для временно созданных файлов (читай this ).
  • Добавьте резервную библиотеку, условно для старых Pythons, с install_requires=[" importlib_resources ; python_version<'3.7'"] (отметьте this , если вы упаковываете свой проект с setuptools<36.2.1).
  • Не забудьте удалить setuptoolsбиблиотека из ваших требований времени выполнения , если вы перешли с традиционного метода.
  • Вы также можете установить zip_safe=True в вашем setup.py.
11 голосов
/ 26 декабря 2017

Если у вас есть такая структура

lidtk
├── bin
│   └── lidtk
├── lidtk
│   ├── analysis
│   │   ├── char_distribution.py
│   │   └── create_cm.py
│   ├── classifiers
│   │   ├── char_dist_metric_train_test.py
│   │   ├── char_features.py
│   │   ├── cld2
│   │   │   ├── cld2_preds.txt
│   │   │   └── cld2wili.py
│   │   ├── get_cld2.py
│   │   ├── text_cat
│   │   │   ├── __init__.py
│   │   │   ├── REAMDE.md   <---------- say you want to get this
│   │   │   └── textcat_ngram.py
│   │   └── tfidf_features.py
│   ├── data
│   │   ├── __init__.py
│   │   ├── create_ml_dataset.py
│   │   ├── download_documents.py
│   │   ├── language_utils.py
│   │   ├── pickle_to_txt.py
│   │   └── wili.py
│   ├── __init__.py
│   ├── get_predictions.py
│   ├── languages.csv
│   └── utils.py
├── README.md
├── setup.cfg
└── setup.py

, вам понадобится этот код:

import pkg_resources

# __name__ in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/REAMDE.md'  # always use slash
filepath = pkg_resources.resource_filename(__name__, path)

Я не слишком уверен насчет части "всегда использовать косую черту".Это может быть от setuptools

Также обратите внимание, что если вы используете пути, вы должны использовать косую черту (/) в качестве разделителя пути, даже если вы находитесь в Windows,Setuptools автоматически конвертирует косые черты в соответствующие платформенные разделители во время сборки

Если вам интересно, где находится документация:

5 голосов
/ 07 августа 2018

Содержание в «10.8. Чтение файлов данных в пакете» Python Cookbook, третье издание Дэвида Бизли и Брайана К. Джонса, дающее ответы.

Я просто получу это здесь:

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

mypackage/
    __init__.py
    somedata.dat
    spam.py

Теперь предположим, что файл spam.py хочет прочитать содержимое файла somedata.dat. Сделать используйте следующий код:

import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')

Результирующая переменная data будет байтовой строкой, содержащей необработанное содержимое файла.

Первый аргумент get_data () - это строка, содержащая имя пакета. Вы можете либо предоставьте его напрямую, либо используйте специальную переменную, такую ​​как __package__. Второй Аргумент - это относительное имя файла в пакете. При необходимости вы можете перемещаться в разные каталоги, используя стандартные соглашения об именах файлов Unix, пока Последний каталог все еще находится в пакете.

Таким образом, пакет может быть установлен как каталог, .zip или .egg.

3 голосов
/ 17 мая 2011

Каждый модуль Python в вашем пакете имеет атрибут __file__

. Вы можете использовать его как:

import os 
from mypackage

templates_dir = os.path.join(os.path.dirname(mypackage.__file__), 'templates')
template_file = os.path.join(templates_dir, 'template.txt')

Ресурсы для яиц см.

0 голосов
/ 17 мая 2011

при условии, что вы используете файл яйца;не извлечено:

Я «решил» это в недавнем проекте, используя скрипт postinstall, который извлекает мои шаблоны из яйца (zip-файла) в соответствующий каталог в файловой системе.Это было самое быстрое и надежное решение, которое я нашел, поскольку работа с __path__[0] иногда может идти не так (я не помню имя, но я наткнулся на хотя бы одну библиотеку, которая добавила что-то перед этим списком!).

Также файлы с яйцами обычно извлекаются на лету во временное место, называемое «тайником с яйцами».Вы можете изменить это местоположение, используя переменную окружения, либо перед запуском скрипта, либо даже позже, например.

os.environ['PYTHON_EGG_CACHE'] = path

Однако есть pkg_resources , которые могут выполнить работу должным образом.

0 голосов
/ 17 мая 2011
0 голосов
/ 17 мая 2011

Вы должны иметь возможность импортировать части пространства имен вашего пакета с чем-то вроде:

from my_package import my_stuff

... вам не нужно указывать что-то похожее на имя файла, если это правильно сконструированный Pythonпакет (который обычно абстрагируется).

0 голосов
/ 17 мая 2011

[добавлено 2016-06-15: очевидно, это работает не во всех ситуациях. пожалуйста, обратитесь к другим ответам]


import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...