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

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

Моя ситуация

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

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

package\templates\temp_file

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

75
задан Martin Thoma 13 December 2017 в 13:38
поделиться

1 ответ

Упаковочная вводная часть:

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

Структурируют Ваш проект как это, помещая файлы данных в подкаталог в [1 129] пакет:

.
├── package
│   ├── __init__.py
│   ├── templates
│   │   └── temp_file
│   ├── mymodule1.py
│   └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py

необходимо передать include_package_data=True в эти setup() вызов. Файл манифеста только необходим, если Вы хотите использовать setuptools/distutils и исходные дистрибутивы сборки. Удостоверяться эти templates/temp_file упаковывается для этой структуры проекта в качестве примера, добавьте строку как это в файл манифеста:

recursive-include package *

Историческое примечание хлама: Используя файл манифеста не нужно для современных бэкендов сборки , таких как быстрое движение, поэзия, которая будет включать файлы данных пакета по умолчанию. Так, если Вы используете pyproject.toml, и Вы не имеете setup.py файл затем, можно проигнорировать весь материал приблизительно [1 112].

Теперь, с упаковкой из пути, на часть чтения...

Рекомендация:

Пользуются стандартной библиотекой pkgutil API. Это собирается быть похожим на это в коде библиотеки:

# within package/mymodule1.py, for example
import pkgutil

data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))

Это работает в zip. Это работает над Python 2 и Python 3. Это не требует сторонних зависимостей. Я действительно не знаю ни о каких оборотных сторонах (если Вы, затем прокомментируйте ответ).

Плохие способы избежать:

Плохой способ № 1: использование относительных путей от исходного файла

Это в настоящее время - принятый ответ. В лучшем случае это выглядит примерно так:

from pathlib import Path

resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))

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

Плохой способ № 2: использование pkg_resources API

Это в настоящее время - проголосовавший вершине ответ. Это выглядит примерно так:

from pkg_resources import resource_string

data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))

Что случилось с этим? Это добавляет время выполнения зависимость от [1 123] setuptools, который должен предпочтительно быть установка зависимость времени только. Импорт и использование pkg_resources могут стать действительно медленными, поскольку код создает рабочий набор [1 133] весь установленные пакеты, даже при том, что Вы только интересовались [1 134] Ваше собственное ресурсы пакета. Это не грандиозное предприятие во время установки (так как установка выключена), но это ужасно во времени выполнения.

Плохой способ № 3: использование importlib.resources API

Это - недавнее стандартная библиотека дополнение ( новый в Python 3.7 ), но существует бэкпорт, доступный также. Это похоже на это:

try:
    from importlib.resources import read_binary
    from importlib.resources import read_text
except ImportError:
    # Python 2.x backport
    from importlib_resources import read_binary
    from importlib_resources import read_text

data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))

Что случилось с этим? Ну, к сожалению, это еще не работает.... Это - все еще неполный API, использование importlib.resources потребует, чтобы Вы добавили пустой файл templates/__init__.py, чтобы файлы данных находились в подпакете, а не в подкаталоге. Это также выставит package/templates подкаталог как разрешенное к ввозу package.templates подпакет самостоятельно. Если это не грандиозное предприятие, и оно не беспокоит Вас, то можно идти вперед и добавить __init__.py файл там и использовать систему импорта для доступа к ресурсам. Однако, в то время как Вы в нем, можно также превратить его в my_resources.py файл вместо этого, и просто определить некоторые байты или строковые переменные в модуле, затем импортировать их в коде Python. Это - система импорта, делающая тяжелый подъем здесь так или иначе.

проект В качестве примера:

я создал проект в качестве примера на [1 125] github и загрузил на [1 126] PyPI, который демонстрирует все четыре подхода, обсужденные выше. Испытайте его с:

$ pip install resources-example
$ resources-example

См. https://github.com/wimglenn/resources-example для большего количества информации

2
ответ дан 24 November 2019 в 11:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: