Не могли бы вы сказать мне, как я могу прочитать файл, который находится внутри моего пакета Python?
Моя ситуация
Пакет, который я загружаю, имеет ряд шаблонов (текстовые файлы, используемые как строки), которые я хочу загрузить из программы. Но как указать путь к такому файлу?
Представьте, что я хочу прочитать файл из:
package\templates\temp_file
Какая-то манипуляция с путями? Отслеживание базового пути пакета?
Ответы:
[добавлено 15.06.2016: очевидно, это работает не во всех ситуациях. пожалуйста, обратитесь к другим ответам]
источник
TL; DR; Используйте
importlib.resources
модуль стандартной библиотеки, как описано в методе № 2 ниже.Традиционная
pkg_resources
отsetuptools
не рекомендуется больше , потому что новый метод:setuptools
), а полагайтесь только на стандартную библиотеку Python.Я сохранил традиционное перечисление первым, чтобы объяснить различия с новым методом при переносе существующего кода (перенос также объясняется здесь ).
Предположим, что ваши шаблоны находятся в папке, вложенной в пакет вашего модуля:
1) Использование
pkg_resources
изsetuptools
(медленно)Вы можете использовать
pkg_resources
пакет из дистрибутива setuptools , но это требует затрат с точки зрения производительности :... и обратите внимание, что, согласно Setuptools /
pkg_resources
docs, вы не должны использоватьos.path.join
:2) Python> = 3.7 или с использованием
importlib_resources
библиотеки с обратным переносомИспользуйте
importlib.resources
модуль стандартной библиотеки, который более эффективен, чемsetuptools
приведенный выше:Для примера, заданного в вопросе, теперь мы должны:
<your_package>/templates/
в правильный пакет, создав в нем пустой__init__.py
файл,import
оператор (больше не нужно разбирать имена пакетов / модулей),resource_name = "temp_file"
(без пути).источник
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
есть идеи?importlib.resources
иpkg_resources
это не всегда совместимы .importlib.resources
работает с добавленнымиsys.path
zip-pkg_resources
файлами, средствами настройки и работает с файлами яиц, которые представляют собой zip-файлы, хранящиеся в каталоге, в который он сам добавляетсяsys.path
. Напримерsys.path = [..., '.../foo', '.../bar.zip']
, яйца входят.../foo
, но пакетыbar.zip
также можно импортировать. Вы не можете использоватьpkg_resources
для извлечения данных из пакетов вbar.zip
. Я не проверял, регистрирует ли setuptools необходимый загрузчик дляimportlib.resources
работы с яйцами.Package has no location
появлении ошибки ?templates
в примере), вы можете установитьpackage
аргумент__package__
, например,pkg_resources.read_text(__package__, 'temp_file')
Прелюдия к упаковке:
Прежде чем вы сможете даже беспокоиться о чтении файлов ресурсов, первый шаг - убедиться, что файлы данных в первую очередь упаковываются в ваш дистрибутив - их легко читать прямо из дерева исходных текстов, но важная часть - это сделать убедитесь, что эти файлы ресурсов доступны из кода в установленном пакете.
Структурируйте свой проект следующим образом, поместив файлы данных в подкаталог внутри пакета:
Вы должны пройти
include_package_data=True
вsetup()
вызове. Файл манифеста необходим только в том случае, если вы хотите использовать setuptools / distutils и создавать исходные дистрибутивы. Чтобы убедиться, чтоtemplates/temp_file
файлы упакованы для этого примера структуры проекта, добавьте такую строку в файл манифеста:Историческое примечание: использование файла манифеста не требуется для современных бэкэндов сборки, таких как flit, поэзия, которые по умолчанию будут включать файлы данных пакета. Итак, если вы используете
pyproject.toml
и у вас нетsetup.py
файла, вы можете игнорировать все, что связано сMANIFEST.in
.Теперь, когда упаковка убрана, перейдем к читающей части ...
Рекомендация:
Используйте стандартные библиотечные
pkgutil
API. В коде библиотеки это будет выглядеть так:Работает на молнии. Он работает на Python 2 и Python 3. Не требует сторонних зависимостей. Я не особо осведомлен о каких-либо недостатках (если да, то прокомментируйте ответ).
Плохие способы избежать:
Плохой способ no1: использование относительных путей из исходного файла
В настоящее время это принятый ответ. В лучшем случае это выглядит примерно так:
Что случилось с этим? Предположение о том, что у вас есть доступные файлы и подкаталоги, неверно. Этот подход не работает, если выполняется код, который упакован в zip-архив или колесико, и может быть полностью вне контроля пользователя, будет ли вообще извлечен ваш пакет в файловую систему или нет.
Плохой способ no 2: использование API pkg_resources
Это описано в ответе, получившем наибольшее количество голосов. Это выглядит примерно так:
Что случилось с этим? Он добавляет зависимость времени выполнения от setuptools , которая предпочтительно должна быть зависимостью только от времени установки . Импорт и использование
pkg_resources
могут стать очень медленными, поскольку код создает рабочий набор из всех установленных пакетов, даже если вас интересуют только собственные ресурсы пакета. Это не имеет большого значения во время установки (поскольку установка выполняется один раз), но это некрасиво во время выполнения.Плохой способ no 3: использование API importlib.resources
В настоящее время это рекомендация в ответе, получившем наибольшее количество голосов. Это недавнее добавление стандартной библиотеки ( новое в Python 3.7 ). Выглядит это так:
Что случилось с этим? Ну, к сожалению, не работает ... пока. Это все еще неполный 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, которые вы можете использовать следующим образом:Это работает на Python 2 и 3, работает в zip-
__init__.py
архивах и не требует добавления ложных файлов в подкаталоги ресурсов. Единственный недостаток,pkgutil
который я вижу, заключается в том, что эти новые API еще не появились в stdlib, поэтому все еще существует сторонняя зависимость. Новые API-интерфейсыimportlib_resources
должны поступать в stdlibimportlib.resources
в Python 3.9.Пример проекта:
Я создал пример проекта на github и загрузил его на PyPI , который демонстрирует все пять подходов, описанных выше. Попробуйте:
См. Https://github.com/wimglenn/resources-example для получения дополнительной информации.
источник
importlib.resources
несмотря на все эти недостатки, неполный API, который уже ожидает прекращения поддержки ? Новее не обязательно лучше. Скажите, какие преимущества он на самом деле предлагает по сравнению с stdlib pkgutil, о котором в вашем ответе не упоминается?pkgutil.get_data()
API подтвердил мое чутье - это неразвитый API, который не рекомендуется использовать. Тем не менее, я согласен с вами,importlib.resources
это не намного лучшая альтернатива, но до тех пор, пока PY3.10 не решит это, я придерживаюсь этого выбора, он узнал, что это не просто еще один «стандарт», рекомендованный документами.pkgutil
вообще не упоминается в графике устаревания PEP 594 - Удаление разряженных батарей из стандартной библиотеки и вряд ли будет удалено без уважительной причины. Он существует с Python 2.3 и указан как часть протокола загрузчика в PEP 302 . Использование «недоопределенного API» - не очень убедительный ответ, который может описать большую часть стандартной библиотеки Python!pkgutil
во всех отношениях. Ваше "чутье" и обращение к авторитету для меня бессмысленно, если есть проблемы сget_data
загрузчиками, то покажите доказательства и практические примеры.Если у вас есть эта структура
вам нужен этот код:
Странная часть "всегда использовать косую черту" пришла из
setuptools
APIЕсли вам интересно, где находится документация:
источник
pkg_resources
имеет накладные расходы, которыеpkgutil
преодолевают. Кроме того, если предоставленный код запускается как точка входа,__name__
будет оцениваться значение__main__
, а не имя пакета.Содержимое в «10.8. Чтение файлов данных в пакете» Python Cookbook, третье издание Дэвида Бизли и Брайана К. Джонса дает ответы.
Я просто доставлю это сюда:
Предположим, у вас есть пакет с файлами, организованными следующим образом:
Теперь предположим, что файл spam.py хочет прочитать содержимое файла somedata.dat. Для этого используйте следующий код:
Результирующие данные переменных будут байтовой строкой, содержащей необработанное содержимое файла.
Первый аргумент get_data () - это строка, содержащая имя пакета. Вы можете указать его напрямую или использовать специальную переменную, например
__package__
. Второй аргумент - это относительное имя файла в пакете. При необходимости вы можете переходить в разные каталоги, используя стандартные соглашения об именах файлов Unix, если последний каталог все еще находится внутри пакета.Таким образом, пакет может быть установлен как каталог, .zip или .egg.
источник
Каждый модуль python в вашем пакете имеет
__file__
атрибутВы можете использовать его как:
Ресурсы для яиц см. На сайте http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources.
источник
Принятый ответ следует использовать
importlib.resources
.pkgutil.get_data
также требует, чтобы аргументpackage
был пакетом, не относящимся к пространству имен ( см. документацию pkgutil ). Следовательно, каталог, содержащий ресурс, должен иметь__init__.py
файл, поэтому он имеет те же ограничения, что иimportlib.resources
. Если проблема накладных расходовpkg_resources
не вызывает беспокойства, это также приемлемая альтернатива.Pre-Python-3.3
, все пакеты должны были иметь расширение__init__.py
.Post-Python-3.3
, папка не обязательно__init__.py
должна быть пакетом. Это называется файломnamespace package
. К сожалению,pkgutil
не работает сnamespace packages
( см. Pkgutil docs ).Например, со структурой пакета:
где
hi.txt
только чтоHi!
, вы получите следующееТем не менее, с
__init__.py
инbar
, вы получитеисточник
importlib.resources
, которогоpkgutil
нет, заключалось в том, что каталог, содержащий ресурсы,__init__.py
тоже должен иметь свой , т.е. он должен быть подпакетом . Это не связано с проблемами пакета пространства имен, которые касаются того, существует ли__init__.py
каталог верхнего уровня, а не подкаталоги данных внутри пакета.pre-Python 3.3+
, все пакеты должны были__init__.py
быть загружены. После 3.3 пакетам они не нужны. Пакеты без__init__.py
являютсяnamespace packages
. Согласноpkgutil
документации, если вы попытаетесь загрузить ресурс из пакета пространства имен, вы получитеNone
. См. Мой обновленный отредактированный ответ.pkgutil
неправильно. Попробуйте сpkgutil.get_data("foo", "bar/hi.txt")
предполагая, что вы используете файл с яйцом; не извлекается:
Я «решил» это в недавнем проекте, используя сценарий postinstall, который извлекает мои шаблоны из яйца (zip-файла) в соответствующий каталог в файловой системе. Это было самое быстрое и самое надежное решение, которое я нашел, поскольку работа с ним
__path__[0]
иногда может пойти не так (я не помню название, но я наткнулся на хотя бы одну библиотеку, которая добавила что-то перед этим списком!).Также файлы яиц обычно извлекаются «на лету» во временное место, называемое «кэш яиц». Вы можете изменить это местоположение с помощью переменной окружения либо перед запуском вашего скрипта, либо даже позже, например.
Однако есть pkg_resources, который может правильно выполнить эту работу.
источник