Прелюдия к упаковке:
Прежде чем вы сможете даже беспокоиться о чтении файлов ресурсов, первый шаг - убедиться, что файлы данных в первую очередь упаковываются в ваш дистрибутив - их легко читать прямо из дерева исходных текстов, но важная часть - это сделать убедитесь, что эти файлы ресурсов доступны из кода в установленном пакете.
Структурируйте свой проект следующим образом, поместив файлы данных в подкаталог внутри пакета:
.
├── 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 *
Историческое примечание: использование файла манифеста не требуется для современных бэкэндов сборки, таких как flit, поэзия, которые по умолчанию будут включать файлы данных пакета. Итак, если вы используете pyproject.toml
и у вас нет setup.py
файла, вы можете игнорировать все, что связано с MANIFEST.in
.
Теперь, когда упаковка убрана, перейдем к читающей части ...
Рекомендация:
Используйте стандартные библиотечные pkgutil
API. В коде библиотеки это будет выглядеть так:
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
Работает на молнии. Он работает на Python 2 и Python 3. Не требует сторонних зависимостей. Я не особо осведомлен о каких-либо недостатках (если да, то прокомментируйте ответ).
Плохие способы избежать:
Плохой способ no1: использование относительных путей из исходного файла
В настоящее время это принятый ответ. В лучшем случае это выглядит примерно так:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
Что случилось с этим? Предположение о том, что у вас есть доступные файлы и подкаталоги, неверно. Этот подход не работает, если выполняется код, который упакован в zip-архив или колесико, и может быть полностью вне контроля пользователя, будет ли вообще извлечен ваш пакет в файловую систему или нет.
Плохой способ no 2: использование API pkg_resources
Это описано в ответе, получившем наибольшее количество голосов. Это выглядит примерно так:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
Что случилось с этим? Он добавляет зависимость времени выполнения от setuptools , которая предпочтительно должна быть зависимостью только от времени установки . Импорт и использование pkg_resources
могут стать очень медленными, поскольку код создает рабочий набор из всех установленных пакетов, даже если вас интересуют только собственные ресурсы пакета. Это не имеет большого значения во время установки (поскольку установка выполняется один раз), но это некрасиво во время выполнения.
Плохой способ no 3: использование API importlib.resources
В настоящее время это рекомендация в ответе, получившем наибольшее количество голосов. Это недавнее добавление стандартной библиотеки ( новое в Python 3.7 ). Выглядит это так:
from importlib.resources import read_binary
data = read_binary("package.templates", "temp_file")
Что случилось с этим? Ну, к сожалению, не работает ... пока. Это все еще неполный API, для использования importlib.resources
потребуется добавить пустой файл templates/__init__.py
, чтобы файлы данных находились внутри подпакета, а не в подкаталоге. Он также package/templates
раскроет подкаталог как самостоятельный импортируемый подпакет package.templates
. Если это не имеет большого значения и вас это не беспокоит, вы можете продолжить и добавить __init__.py
туда файл и использовать систему импорта для доступа к ресурсам. Однако, пока вы занимаетесь этим, вы также можете превратить его в my_resources.py
файл и просто определить некоторые байты или строковые переменные в модуле, а затем импортировать их в код Python. В любом случае, это система импорта, которая делает здесь тяжелую работу.
Достойное упоминание: использование новых API importlib_resources
Об этом еще не упоминалось ни в каких других ответах, но importlib_resources
это больше, чем простой бэкпорт importlib.resources
кода Python 3.7+ . У него есть проходимые API, которые вы можете использовать следующим образом:
import importlib_resources
my_resources = importlib_resources.files("package")
data = (my_resources / "templates" / "temp_file").read_bytes()
Это работает на Python 2 и 3, работает в zip- __init__.py
архивах и не требует добавления ложных файлов в подкаталоги ресурсов. Единственный недостаток, pkgutil
который я вижу, заключается в том, что эти новые API еще не появились в stdlib, поэтому все еще существует сторонняя зависимость. Новые API-интерфейсы importlib_resources
должны поступать в stdlib importlib.resources
в Python 3.9.
Пример проекта:
Я создал пример проекта на github и загрузил его на PyPI , который демонстрирует все пять подходов, описанных выше. Попробуйте:
$ pip install resources-example
$ resources-example
См. Https://github.com/wimglenn/resources-example для получения дополнительной информации.