Подтвердить что ты не робот

Как читать (статический) файл из пакета Python?

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

Моя ситуация

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

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

package\templates\temp_file

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

4b9b3361

Ответ 1

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


import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')

Ответ 2

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

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


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

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

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

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

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

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

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 в вашем zip_safe=True setup.py и/или использовать долгожданный упаковщик zipapp из python-3.5 для создания автономных дистрибутивов.

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

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

Основной доступ к ресурсам

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

2) Python> = 3.7, или с importlib_resources библиотеки 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  # the package containing the file

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(), так как теперь контекстные менеджеры используются для временно созданных файлов (прочитайте это).
  • Добавьте резервную библиотеку, условно для более старых Pythons, с install_requires=[" importlib_resources; python_version<'3.7'"] (отметьте это, если вы упаковываете свой проект с setuptools<36.2.1).
  • Не забудьте удалить библиотеку setuptools из ваших требований времени выполнения, если вы перешли с традиционного метода.
  • Вы также можете установить zip_safe=True в вашем setup.py.

Ответ 3

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

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 автоматически конвертирует косые черты в соответствующие платформенные разделители во время сборки

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

Ответ 4

Содержание в "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.

Ответ 5

Каждый модуль 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')

Для ресурсов яйца см.: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources

Ответ 6

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

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

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

os.environ['PYTHON_EGG_CACHE'] = path

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

Ответ 8

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

from my_package import my_stuff

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