Прелюдія упаковки:
Перш ніж ви навіть можете потурбуватися про читання файлів ресурсів, перший крок - переконатися, що файли даних в першу чергу потрапляють у ваш дистрибутив - їх легко читати безпосередньо з вихідного дерева, але важлива частина - це зробити переконайтеся, що ці файли ресурсів доступні з коду в межах встановленого пакету.
Структуруйте свій проект таким чином, вводячи файли даних у підкаталог всередині пакета:
.
├── 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")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
Працює на блискавках. Він працює на 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))
Що з цим? Припущення про наявність у вас файлів та підкаталогів є невірним. Цей підхід не працює, якщо виконання коду, упакованого в поштовий індекс або колесо, і він може повністю вийти з-під контролю користувача, незалежно від того, отриманий ваш пакет у файлову систему.
Поганий спосіб №2: використання API-файлів pkg_resources
Про це йдеться у відповіді на голоси. Це виглядає приблизно так:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
Що з цим? Він додає залежність часу виконання від setuptools , яка, як правило , повинна бути лише залежною від часу встановлення . Імпорт та використання pkg_resources
можуть стати дуже повільними, оскільки код створює робочий набір усіх встановлених пакетів, навіть якщо вас цікавили лише ваші власні ресурси пакету. Це не є великою справою під час встановлення (оскільки установка одноразова), але це некрасиво під час виконання.
Поганий спосіб №3: використання API importlib.resources
Наразі це рекомендація у відповіді на голоси. Це нещодавнє стандартне доповнення бібліотеки ( нове в 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. Це система імпорту в будь-якому випадку робить важкий підйом.
Приклад проекту:
Я створив приклад проекту на github і завантажив на PyPI , де демонструються всі чотири підходи, обговорені вище. Спробуйте це:
$ pip install resources-example
$ resources-example
Для отримання додаткової інформації див. Https://github.com/wimglenn/resources-example .