Автор оригинала: Doug Hellmann.
Цель:
Добавить в путь поиска модуля для конкретного пакета и работа с ресурсами, входящими в пакет.
Модуль pkgutil
включает функции для изменения правил импорта для пакетов Python и для загрузки ресурсов, не связанных с кодом, из файлов, распространяемых внутри пакета.
Пути импорта пакетов
Функция extend_path ()
используется для изменения пути поиска и изменения способа импорта подмодулей из пакета, чтобы несколько разных каталогов можно было объединить, как если бы они были одним. Это можно использовать для переопределения установленных версий пакетов с версиями для разработки или для объединения специфичных для платформы и общих модулей в единое пространство имен пакетов.
Наиболее распространенный способ вызова extend_path ()
– это добавление двух строк к __init__.py
внутри пакета.
import pkgutil __path__ = pkgutil.extend_path(__path__, __name__)
extend_path ()
сканирует sys.path
на предмет каталогов, которые включают подкаталог, названный для пакета, указанного в качестве второго аргумента. Список каталогов объединяется со значением пути, переданным в качестве первого аргумента, и возвращается в виде единого списка, подходящего для использования в качестве пути импорта пакета.
Пример пакета с именем demopkg
включает два файла: __init__.py
и shared.py
. Файл __init__.py
в demopkg1
содержит операторы print
, чтобы показать путь поиска до и после его изменения, чтобы подчеркнуть разницу.
demopkg1/__ init__.py
import pkgutil import pprint print('demopkg1.__path__ before:') pprint.pprint(__path__) print() __path__ pkgutil.extend_path(__path__, __name__) print('demopkg1.__path__ after:') pprint.pprint(__path__) print()
Каталог extension
с дополнительными функциями для demopkg
содержит еще три исходных файла. На каждом уровне каталога есть __init__.py
и not_shared.py
.
$ find extension -name '*.py' extension/__init__.py extension/demopkg1/__init__.py extension/demopkg1/not_shared.py
Эта простая тестовая программа импортирует пакет demopkg1
.
pkgutil_extend_path.py
import demopkg1 print('demopkg1 :', demopkg1.__file__) try: import demopkg1.shared except Exception as err: print('demopkg1.shared : Not found ({})'.format(err)) else: print('demopkg1.shared :', demopkg1.shared.__file__) try: import demopkg1.not_shared except Exception as err: print('demopkg1.not_shared: Not found ({})'.format(err)) else: print('demopkg1.not_shared:', demopkg1.not_shared.__file__)
Когда эта тестовая программа запускается непосредственно из командной строки, модуль not_shared
не обнаруживается.
Примечание
Полные пути к файловой системе в этих примерах были сокращены, чтобы выделить изменяющиеся части.
$ python3 pkgutil_extend_path.py demopkg1.__path__ before: ['.../demopkg1'] demopkg1.__path__ after: ['.../demopkg1'] demopkg1 : .../demopkg1/__init__.py demopkg1.shared : .../demopkg1/shared.py demopkg1.not_shared: Not found (No module named 'demopkg1.not_sh ared')
Однако, если каталог extension
добавляется в PYTHONPATH
и программа запускается снова, будут получены другие результаты.
$ demopkg1.__path__ before: ['.../demopkg1'] demopkg1.__path__ after: ['.../demopkg1', '.../extension/demopkg1'] demopkg1 : .../demopkg1/__init__.py demopkg1.shared : .../demopkg1/shared.py demopkg1.not_shared: .../extension/demopkg1/not_shared.py
Версия demopkg1
внутри каталога extension
была добавлена в путь поиска, поэтому модуль not_shared
находится там.
Расширение пути таким образом полезно для объединения версий пакетов для конкретной платформы с общими пакетами, особенно если версии для конкретной платформы включают модули расширения C.
Версии пакетов для разработки
При разработке улучшений проекта часто возникает необходимость протестировать изменения в установленном пакете. Замена установленной копии на версию для разработки может быть плохой идеей, поскольку это не обязательно правильно, а другие инструменты в системе, вероятно, будут зависеть от установленного пакета.
Полностью отдельная копия пакета может быть настроена в среде разработки с использованием virtualenv
или venv, но для небольших модификаций накладные расходы на настройку виртуальной среды со всеми зависимостями могут быть чрезмерными.
Другой вариант – использовать pkgutil
для изменения пути поиска модулей для модулей, принадлежащих разрабатываемому пакету. Однако в этом случае путь должен быть изменен на противоположный, чтобы версия для разработки переопределила установленную версию.
Учитывая пакет demopkg2
, содержащий __init__.py
и overloaded.py
, с разрабатываемой функцией, расположенной в demopkg2/overloaded.py
. Установленная версия содержит
demopkg2/overloaded.py
def func(): print('This is the installed version of func().')
и demopkg2/__ init__.py
содержит
demopkg2/__ init__.py
import pkgutil __path__ pkgutil.extend_path(__path__, __name__) __path__.reverse()
reverse ()
используется для обеспечения того, чтобы все каталоги, добавленные к пути поиска с помощью pkgutil
, сканировались на предмет импорта до расположения по умолчанию.
Эта программа импортирует demopkg2.overloaded
и вызывает func ()
.
pkgutil_devel.py
import demopkg2 print('demopkg2 :', demopkg2.__file__) import demopkg2.overloaded print('demopkg2.overloaded:', demopkg2.overloaded.__file__) print() demopkg2.overloaded.func()
Запуск его без какой-либо специальной обработки пути дает результат установленной версии func ()
.
$ python3 pkgutil_devel.py demopkg2 : .../demopkg2/__init__.py demopkg2.overloaded: .../demopkg2/overloaded.py This is the installed version of func().
Каталог разработки, содержащий
$ find develop/demopkg2 -name '*.py' develop/demopkg2/__init__.py develop/demopkg2/overloaded.py
и модифицированная версия перегруженного
разработка/demopkg2/overloaded.py
def func(): print('This is the development version of func().')
будет загружен, когда тестовая программа будет запущена с каталогом develop
в пути поиска.
$ demopkg2 : .../demopkg2/__init__.py demopkg2.overloaded: .../develop/demopkg2/overloaded.py This is the development version of func().
Управление путями с помощью файлов PKG
В первом примере показано, как расширить путь поиска с помощью дополнительных каталогов, включенных в PYTHONPATH
. Также можно добавить в путь поиска, используя файлы * .pkg
, содержащие имена каталогов. Файлы PKG похожи на файлы PTH, используемые модулем сайта. Они могут содержать имена каталогов, по одному в каждой строке, которые нужно добавить в путь поиска для пакета.
Другой способ структурировать части приложения, зависящие от платформы, из первого примера – использовать отдельный каталог для каждой операционной системы и включать файл .pkg
для расширения пути поиска.
В этом примере используются те же файлы demopkg1
, а также включены следующие файлы.
$ find os_* -type f os_one/demopkg1/__init__.py os_one/demopkg1/not_shared.py os_one/demopkg1.pkg os_two/demopkg1/__init__.py os_two/demopkg1/not_shared.py os_two/demopkg1.pkg
Файлы PKG называются demopkg1.pkg
в соответствии с расширяемым пакетом. Оба они содержат одну строку.
demopkg
Эта демонстрационная программа показывает версию импортируемого модуля.
pkgutil_os_specific.py
import demopkg1 print('demopkg1:', demopkg1.__file__) import demopkg1.shared print('demopkg1.shared:', demopkg1.shared.__file__) import demopkg1.not_shared print('demopkg1.not_shared:', demopkg1.not_shared.__file__)
Для переключения между двумя пакетами можно использовать простой сценарий-оболочку.
with_os.sh
#!/bin/sh export PYTHONPATHos_${1} echo $PYTHONPATH" echo python3 pkgutil_os_specific.py
При запуске с аргументами "one"
или "two"
путь корректируется.
$ ./with_os.sh one demopkg1.__path__ before: ['.../demopkg1'] demopkg1.__path__ after: ['.../demopkg1', '.../os_one/demopkg1', 'demopkg'] demopkg1: .../demopkg1/__init__.py demopkg1.shared: .../demopkg1/shared.py demopkg1.not_shared: .../os_one/demopkg1/not_shared.py $ ./with_os.sh two demopkg1.__path__ before: ['.../demopkg1'] demopkg1.__path__ after: ['.../demopkg1', '.../os_two/demopkg1', 'demopkg'] demopkg1: .../demopkg1/__init__.py demopkg1.shared: .../demopkg1/shared.py demopkg1.not_shared: .../os_two/demopkg1/not_shared.py
Файлы PKG могут появляться в любом месте обычного пути поиска, поэтому один файл PKG в текущем рабочем каталоге также может использоваться для включения дерева разработки.
Вложенные пакеты
Для вложенных пакетов необходимо только изменить путь к пакету верхнего уровня. Например, с этой структурой каталогов
$ find nested -name '*.py' nested/__init__.py nested/second/__init__.py nested/second/deep.py nested/shallow.py
Где nested/__ init__.py
содержит
вложенный/__ init__.py
import pkgutil __path__ pkgutil.extend_path(__path__, __name__) __path__.reverse()
и дерево развития вроде
$ find develop/nested -name '*.py' develop/nested/__init__.py develop/nested/second/__init__.py develop/nested/second/deep.py develop/nested/shallow.py
Оба модуля shallow
и deep
содержат простую функцию для печати сообщения, указывающего, исходят ли они из установленной или разрабатываемой версии.
Эта тестовая программа проверяет новые пакеты.
pkgutil_nested.py
import nested import nested.shallow print('nested.shallow:', nested.shallow.__file__) nested.shallow.func() print() import nested.second.deep print('nested.second.deep:', nested.second.deep.__file__) nested.second.deep.func()
Когда pkgutil_nested.py
запускается без изменения пути, используются установленные версии обоих модулей.
$ python3 pkgutil_nested.py nested.shallow: .../nested/shallow.py This func() comes from the installed version of nested.shallow nested.second.deep: .../nested/second/deep.py This func() comes from the installed version of nested.second.de ep
Когда к пути добавляется каталог develop
, версия для разработки обеих функций переопределяет установленные версии.
$ nested.shallow: .../develop/nested/shallow.py This func() comes from the development version of nested.shallow nested.second.deep: .../develop/nested/second/deep.py This func() comes from the development version of nested.second. deep
Данные пакета
Помимо кода, пакеты Python могут содержать файлы данных, такие как шаблоны, файлы конфигурации по умолчанию, изображения и другие вспомогательные файлы, используемые кодом в пакете. Функция get_data ()
предоставляет доступ к данным в файлах независимо от формата, поэтому не имеет значения, распространяется ли пакет как EGG, часть замороженного двоичного файла или обычные файлы. в файловой системе.
С пакетом pkgwithdata
, содержащим каталог templates
$ find pkgwithdata -type f pkgwithdata/__init__.py pkgwithdata/templates/base.html
Файл pkgwithdata/templates/base.html
содержит простой шаблон HTML.
pkgwithdata/templates/base.html
PyMOTW Template Example Template
This is a sample data file.
Эта программа использует get_data ()
для извлечения содержимого шаблона и его печати.
pkgutil_get_data.py
import pkgutil template pkgutil.get_data('pkgwithdata', 'templates/base.html') print(template.decode('utf-8'))
Аргументы для get_data ()
– это имя пакета, разделенное точками, и имя файла относительно верхней части пакета. Возвращаемое значение представляет собой последовательность байтов, поэтому перед печатью оно декодируется из UTF-8.
$ python3 pkgutil_get_data.pyPyMOTW Template Example Template
This is a sample data file.
get_data ()
не зависит от формата распространения, потому что он использует перехватчики импорта, определенные в PEP 302, для доступа к содержимому пакета. Можно использовать любой загрузчик, который предоставляет перехватчики, включая импортер архива ZIP в zip-файле.
pkgutil_get_data_zip.py
import pkgutil import zipfile import sys # Create a ZIP file with code from the current directory # and the template using a name that does not appear on the # local filesystem. with zipfile.PyZipFile('pkgwithdatainzip.zip', mode'w') as zf: zf.writepy('.') zf.write('pkgwithdata/templates/base.html', 'pkgwithdata/templates/fromzip.html', ) # Add the ZIP file to the import path. sys.path.insert(0, 'pkgwithdatainzip.zip') # Import pkgwithdata to show that it comes from the ZIP archive. import pkgwithdata print('Loading pkgwithdata from', pkgwithdata.__file__) # Print the template body print('\nTemplate:') data pkgutil.get_data('pkgwithdata', 'templates/fromzip.html') print(data.decode('utf-8'))
В этом примере PyZipFile.writepy ()
используется для создания ZIP-архива, содержащего копию пакета pkgwithdata
, включая переименованную версию файла шаблона. Затем он добавляет ZIP-архив в путь импорта перед использованием pkgutil
для загрузки шаблона и его печати. Обратитесь к обсуждению zipfile для получения дополнительных сведений об использовании writepy ()
.
$ python3 pkgutil_get_data_zip.py Loading pkgwithdata from pkgwithdatainzip.zip/pkgwithdata/__init__.pyc Template:PyMOTW Template Example Template
This is a sample data file.
Смотрите также
- Стандартная библиотека документации для pkgutil
- virtualenv – сценарий виртуальной среды Иэна Бикинга.
distutils
– инструменты упаковки из стандартной библиотеки Python.- setuptools – инструменты упаковки нового поколения.
- PEP 302 – обработчики импорта
- zipfile – Создание импортируемых ZIP-архивов.
- zipimport – Импортер пакетов в ZIP-архивах.