Ответ 1
[добавлено 2016-06-15: По-видимому, это не работает во всех ситуациях. обратитесь к другим ответам]
import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')
Не могли бы вы рассказать мне, как я могу прочитать файл, который находится внутри моего пакета Python?
В пакет, который я загружаю, есть несколько шаблонов (текстовых файлов, используемых как строки), которые я хочу загрузить из программы. Но как указать путь к такому файлу?
Представьте, что я хочу прочитать файл:
package\templates\temp_file
Какая-то манипуляция с путями? Отслеживание базового пути пакета?
[добавлено 2016-06-15: По-видимому, это не работает во всех ситуациях. обратитесь к другим ответам]
import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')
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
.
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
для управления путями к ресурсам, так как они не являются путями файловой системы.
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
.
Если у вас есть эта структура
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 автоматически конвертирует косые черты в соответствующие платформенные разделители во время сборки
Если вам интересно, где находится документация:
Содержание в "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.
Каждый модуль 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
при условии, что вы используете файл яйца; не извлекается:
Я решил "это" в недавнем проекте, используя postinstall script, который извлекает мои шаблоны из яйца (zip файл) в соответствующий каталог в файловой системе. Это было самое быстрое и надежное решение, которое я нашел, так как работа с __path__[0]
может иногда ошибаться (я не помню имя, но я купил по крайней мере одну библиотеку, которая добавила что-то перед этим списком!).
Также файлы яйца обычно извлекаются "на лету" во временное место, называемое "кеш яйца". Вы можете изменить это местоположение, используя переменную среды, либо перед запуском script, либо даже позже, например.
os.environ['PYTHON_EGG_CACHE'] = path
Однако есть pkg_resources, который мог бы выполнять работу должным образом.
См.
Вы должны иметь возможность импортировать части пространства имен вашего пакета с чем-то вроде:
from my_package import my_stuff
... вам не нужно указывать что-либо, похожее на имя файла, если это правильно сконструированный пакет Python (который обычно абстрагируется).