Рубрики
Без рубрики

pkgutil – Утилиты пакета

Автор оригинала: 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.py



 
PyMOTW 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.

Смотрите также