InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Встроенная поддержка пакетов в Python 1.5


Начиная с Python версии 1.5a4, в интерпретатор встроена поддержка пакетов. Она реализует немного упрощенную и модифицированную версию семантики импорта пакетов, введенную модулем "ni".

"Импортирование пакета" - это метод для структурирования пространств имен модулей Python используя "точечные имена модулей". Например, модуль с именем A.B определяет подмодуль B в пакете A. В то время как использование модулей позволяет авторам других модулей не беспокоиться о глобальных именах остальных, применение точечных имен модулей предохраняет многомодульные пакеты вроде NumPy или PIL от конфликта с именами других модулей.

Еще в версии Python 1.3 была добавлена поддержка импорта пакетов с помощью стандартного библиотечного модуля "ni". (Предполагают, что это аббревиатура для "New Import" - "новое импортирование", но на самом деле, это название связано с Knights Who Say Ni в фильме Monty Python and the Holy Grail, которые после возвращения рыцарей короля Артура с кустарником, сменили имя на Knights Who Say Neeeow ... Wum ... Ping - но это уже другая история.)

Модуль ni является полностью пользовательским кодом за исключением некоторых изменений в разборщике Python (также введенных в 1.3) для обеспечения импорта в виде "import A.B.C" и "from A.B.C import X". Когда ni еще не доступен, использование такого синтаксиса приведет к ошибке времени выполнения "No such module". Если же ni доступен (после выполнения "import ni" до импортирования других модулей), то он будет перехватывать импортирование подмодулей корректных пакетов.

Новая поддержка пакетов напоминает ni, но более гибкая и немного подправленная.

Пример


Допустим, мы хотим разработать пакет единообразной обработки звуковых файлов и данных. Существует много различных форматов звуковых файлов (обычно различаемых по расширению: .wav, .aiff, .au), поэтому нам может понадобиться создание и поддержка растущего набора модулей для конвертации различных форматов файлов. Еще может быть много других операций, которые придется производить над звуковыми данными (напр., смешивание, добавление эха, применение функций эквалайзера, создание искусственных стерео эффектов), так что, потребуется написание бесконечного потока модулей. Ниже приводится возможная структура для наших пакетов (выраженная в терминах иерархических файловых систем):

Sound/				Верхне-уровневый пакет
      __init__.py		Инициализация пакета
      Utils/			Под-пакет для внутреннего использования
            __init__.py
            iobuffer.py
	    errors.py
	    ...
      Formats/			Под-пакет для преобразования форматов файлов
              __init__.py
              wavread.py
	      wavwrite.py
	      aiffread.py
	      aiffwrite.py
	      auread.py
	      auwrite.py
	      ...
      Effects/			Под-пакет для звуковых эффектов
              __init__.py
	      echo.py
	      surround.py
	      reverse.py
	      ...
      Filters/			Под-пакет для фильтров
              __init__.py
              equalizer.py
	      vocoder.py
	      karaoke.py
	      dolby.py
	      ...

Пользователи пакета могут загружать из него отдельные пакеты, например:

import Sound.Effects.echo
Загрузка под-пакета Sound.Effects.echo. На него следует ссылаться по полному имени, напр. Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)

from Sound.Effects import echo
Здесь также загружается под-пакет echo, но теперь он доступен без префикса: echo.echofilter(input, output, delay=0.7, atten=4)

from Sound.Effects.echo import echofilter
Снова загружаем под-пакет echo, но теперь прямо доступна его функция echofilter: echofilter(input, output, delay=0.7, atten=4)

При использовании from package import item, item может быть подмодулем (или под-пакетом) пакета package, или еще каким-нибудь именем, определенным в пакете, подобно функции, классу или переменной. Оператор import сперва проверяет определен ли item в пакете, если нет, то предполагает что это модуль и пытается его загрузить. Если такой модуль не находится, возникает исключение ImportError.

Обратно, при использовании синтаксиса import item.subitem.subsubitem, каждый элемент за исключением последнего должен быть пакетом; последний может быть модулем или пакетом, но не классом, функцией или переменной как в предыдущем случае.

Импортирование * из пакета; атрибут __all__


Что же теперь происходит когда пользователь пишет from Sound.Effects import *? В идеале, можно было бы надеяться, что это как-то передается файловой системе, ищутся подмодули пакета и все они импортируются. К сожалению, такая операция не очень хорошо работает на Mac и Windows, в которых файловая система не имеет точной информации о регистре имен файлов! На этих платформах не существует гарантированного способа узнать следует ли файл ECHO.PY импортировать как модуль echo, Echo или ECHO. (Например, Windows 95 имеет неприятную привычку показывать все имена файлов с первой заглавной буквы.) 8+3 ограничение DOS на имена файлов добавляют другую интересную проблему для длинных имен модулей.

Единственный выход для автора пакета -- предоставить явный индекс пакета. Оператор import использует следующее соглашение: если __init__.py пакета определяет список под именем __all__, то он считается списком имен модулей, которые должны быть импортированы при встрече команды from package import *. Поддержка обновлений этого списка при выходе новых версий пакета является делом его автора. Авторы пакетов могут и не поддерживать этого, если они не видят пользы от импортирования * в своих пакетах. Например, файл Sound/Effects/__init__.py мог бы содержать следующий код:

__all__ = ["echo", "surround", "reverse"]
И это означало бы, что from Sound.Effects import * будет импортировать три перечисленных подмодуля из пакета Sound.

Если __all__ не определен, оператор from Sound.Effects import * не импортирует все подмодули пакета Sound.Effects в текущее пространство имен; он только проверяет, что пакет Sound.Effects был импортирован (возможно, запуском его инициализирующего кода, __init__.py), а затем импортирует любое имя, определенное в пакете. Это включает любые имена (и явно загруженные подмодули), определенные в __init__.py. Это также включает любые подмодули пакета, явно загруженные предыдущим оператором импорта, напр.

import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
В этом примере, модули echo и surround импортируются в текущее пространство имен, поскольку они определены в пакете Sound.Effects где выполнился оператор from...import. (Это работает даже тогда, когда __all__ определен.)

Имейте в виду, что на практику импортирования * из модуля или пакета обычно смотрят с неодобрением, поскольку это часто приводит к коду, читаемому с трудом. Однако, это полезно для экономии печатания в интерактивных сеансах, когда необходимые модули сконструированы так, что экспортируют только имена, следующие определенным шаблонам.

Помните, что нет ничего плохого в использовании from Package import specific_submodule (из пакета импортировать специфический подмодуль)! Фактически, это становится рекомендуемой нотацией, когда импортируемый модуль не нуждается в использовании подмодулей с такими же именами, из других пакетов.

Внутри-пакетные ссылки


Подмодули часто нуждаются в ссылках между собой. Например, модуль окружения, возможно, захочет использовать модуль эха. Фактически, подобные ссылки настолько общи, что оператор импорта сперва сканирует включающий пакет перед тем, как обратиться к пути поиска стандартных модулей. Так, модуль окружения может просто использовать import echo или from echo import echofilter. Если импортируемый модуль не найден в текущем пакете (пакете, в котором текущий модуль является подмодулем), то оператор импорта ищет уже верхне-уровневый модуль с заданным именем.

Когда пакеты структурированы в под-пакеты (как пакет Sound в примере), не существует сокращения для ссылки на подмодули родственных пакетов, нужно использовать полное имя под-пакета. Например, если модуль Sound.Filters.vocoder нуждается в использовании модуля эха в пакете Sound.Effects, то он может написать from Sound.Effects import echo.

(Можно было бы создать нотацию для ссылки в родительский пакет, подобно использованию ".." при обращении к родительскому каталогу в файловой системе Unix и Windows. Фактически, ni поддерживает это, используя __ для пакета, содержащего текущий модуль, __.__ для родительского пакета, и т.д. Эта особенность была отброшена из-за ее неуклюжести; поскольку большинство пакетов будут иметь относительно мелкую подструктуру, это не такая уж большая потеря.)

Детали


Пакеты тоже модули!


Внимание: нижеследующее может запутать тех, кто знаком с нотацией пакетов в Java, которая похожа на нотацию Python, но отличается от нее.

Когда бы ни загружался подмодуль пакета, Python убеждается, что сам пакет был сперва загружен, в случае необходимости подгружает файл __init__.py. Аналогично и для пакетов. Так, при выполнении оператора import Sound.Effects.echo, сперва проверяется был ли загружен Sound, затем Sound.Effects, и только потом удостоверяется, что загружен Sound.Effects.echo (а если еще нет, то подгружает его).

После загрузки, разница между пакетом и модулем минимальна. Фактически, оба представлены в виде модульных объектов, и каждый хранится в таблице загруженных модулей -- sys.modules. Ключи к ним в sys.modules задаются в виде полных, разделенных точками имен модулей (которые не всегда совпадают с теми, что записаны в операторе импорта). Это является также содержимым переменной __name__ (которая дает полное имя модуля или пакета).

Переменная __path__


Единственное различие между пакетами и модулями проявляется в наличии или отсутствии переменной __path__. Она имеется только у пакетов. Эта переменная инициализируется списком из одного элемента, содержащего имя каталога пакета (подкаталог каталога в sys.path). При изменении __path__ меняется список каталогов в которых производится поиск подмодулей пакета. Например, пакет Sound.Effects мог бы содержать подмодули, специфичные для платформы. При этом можно было использовать следующую структуру каталогов:

Sound/
      __init__.py
      Effects/			# Основные версии модулей эффектов
              __init__.py
              echo.py
	      surround.py
	      reverse.py
	      ...
              plat-ix86/	# Модули эффектов, специфичные для Intel x86
	                echo.py
			surround.py
	      plat-PPC/		# Модули эффектов, специфичные для PPC
	                echo.py

Файл Effects/__init__.py мог бы манипулировать своей переменной __path__ так, чтобы подходящие платформо-специфичные поддиректории загружались перед главной директорией Effects, с тем, чтобы реализация определенных эффектов, специфичная для платформы, переопределяла общую (вероятно более медленную) реализацию. Пример:

platform = ...			# Выясни текущую платформу
dirname = __path__[0]		# Главный каталог пакета
__path__.insert(0, os.path.join(dirname, "plat-" + platform))

Если нежелательно, чтобы платформо-специфичные модули прятали основные модули с такими же именами, то следует использовать __path__.append(...) вместо __path__.insert(0, ...).

Примечание: поддиректории plat-* не являются под-пакетами для Effects, файл Sound/Effects/plat-PPC/echo.py соответствует модулю Sound.Effects.echo.py.

Ложные записи в sys.modules


При использовании пакетов, Вы можете неожиданно обнаружить случайные записи в sys.modules, напр., sys.modules['Sound.Effects.string'] может оказаться равным None. Это "косвенный" элемент, образовавшийся из-за того, что некоторые подмодули в пакете Sound.Effects импортировали верхне-уровневый модуль string. Целью этого является важная оптимизация: поскольку оператор импорта не может угадать, локальный ли нужен модуль или глобальный, и еще потому, что правилами определяется приоритет локальных модулей (в том же пакете) над глобальными, с такими же именами, то оператор импорта должен просмотреть путь поиска пакетов прежде, чем станет искать (возможно уже импортированные) глобальные модули. А так как просмотр пути поиска пакетов относительно дорогая операция, и импортирование уже импортированных модулей считается менее накладным (порядка нескольких просмотров словаря), то с оптимизацией все в порядке. Ложные записи позволяют избежать просмотра пути пакета, когда такой же глобальный модуль был со второго раза импортирован подмодулем этого пакета.

Ложные записи создаются только для модулей, найденных на верхнем уровне; если модуль совсем не был найден, то импорт терпит неудачу и оптимизация обычно не нужна. Кроме того, при интерактивном использовании, пользователь мог создать модуль как локальный пакетный подмодуль, и снова повторить импортирование; если бы были сделаны ложные записи, то он не был бы найден. Если пользователь изменяет структуру пакета, путем создания локального подмодуля с тем же именем, что у глобального модуля уже использованного в пакете, то результат обычно известен как "беспорядок", и соответствующее решение состоит в том, чтобы выйти из интерпретатора и запустить его снова.

А что если у меня модуль и пакет имеют одинаковые имена?


У Вас может быть каталог (в sys.path), в котором находится как модуль spam.py так и подкаталог spam, содержащий __init__.py (без __init__.py каталог не распознается как пакет). В этом случае подкаталог имеет приоритет и импортирование spam будет игнорировать файл spam.py, загружая вместо него пакет spam. Если Вы хотите, чтобы модуль spam.py имел приоритет, то он должен находиться в каталоге, встречающемся раньше в sys.path.

(Окончание: порядок поиска определяется списком суффиксов, возвращаемых функцией imp.get_suffixes(). Обычно порядок поиска для суффиксов следующий: ".so", "module.so", ".py", ".pyc". Директории явно не присутствуют в этом списке, но предшествуют всем элементам этого списка.)

Предложение по инсталляции пакетов


Для того, чтобы Python программа использовала пакет, он должен отыскиваться оператором импорта. Другими словами, пакет должен быть подкаталогом каталога в sys.path.

Традиционно, самый простой способ быть уверенным, что пакет находится в sys.path, было либо инсталлирование его в стандартную библиотеку, либо расширение пользовательского sys.path, путем задания его переменной окружения $PYTHONPATH. На практике, оба решения быстро вызывают хаос.

Выделенные каталоги


В Python 1.5 было установлено соглашение, предотвращающее хаос, путем предоставления системному администратору больше контроля. Прежде всего, два дополнительных каталога добавлены в конец пути поиска по умолчанию (четыре, если префикс установки и exec_prefix отличаются). Они зависят от префикса установки (по умолчанию /usr/local):

  • $prefix/lib/python1.5/site-packages
  • $prefix/lib/site-python

Каталог site-packages можно использовать для пакетов, которые вероятно зависят от версий Python (напр., пакет содержащий разделяемые библиотеки или использующий новые особенности). Каталог site-python используется для обратной совместимости с Python 1.4 и для чисто Python пакетов или модулей, не зависящих от используемой версии Python.

Рекомендуемое использование этих каталогов состоит в размещении каждого пакета в собственную поддиректорию внутри каталога site-packages или site-python. Поддиректория должна иметь имя пакета, которое будет доступно как идентификатор Python. После этого, любая Python программа может импортировать модули из пакета путем задания их полных имен. Например, пакет Sound, использованный в примере, могла бы быть инсталлирована в каталог $prefix/lib/python1.5/site-packages/Sound, что сделает возможным операторы импорта подобные import Sound.Effects.echo.

Дополнение уровня косвенности


В некоторых местах желательно инсталлировать пакеты не в упомянутые каталоги, но так, чтобы они были доступны всем Python программам, запускаемым всеми пользователями. Этого можно достичь двумя различными способами:

Символические связи
Если пакет структурирован для импорта составных имен с использованием точек, то поместите символическую связь на его верхне-уровневый каталог в директорию site-packages или site-python. Имя символической связи должно совпадать с именем пакета; например, пакет Sound может иметь символическую связь $prefix/lib/python1.5/site-packages/Sound, указывающую на /usr/home/soundguru/lib/Sound-1.1/src.

Файлы конфигурации пути
Если пакет на самом деле требует добавления одного или более каталогов в sys.path (напр., если он еще не структурирован для поддержки импорта точечных имен), то "файл конфигурации пути" с именем package.pth может быть размещен либо в директории site-python либо в site-packages. Каждая строка в этом файле (за исключением комментариев и пустых строк) считается содержащим имя каталога, которое добавляется в sys.path. Относительные пути допустимы и интерпретируются относительно каталога, содержащего файл .pth.

Файлы .pth читаются в алфавитном порядке, с чувствительностью к регистру букв как у локальной файловой системы. Это означает, что если у Вас возникнет непреодолимое стремление поиграть с порядком, в котором ищутся каталоги, то, по крайней мере, Вы можете этим заниматься предсказуемым способом. (Это не поощрение. Типичная инсталляция не должна иметь .pth файлов, а если они все-таки имеются, то их должно быть очень немного, иначе что-то не в порядке, а если Вам нужно поиграть с порядком поиска, то что-то совсем не в порядке. Тем не менее, иногда возникает в этом нужда, и теперь Вы знаете как это сделать, если придется.)

Примечание для платформ Mac и Windows


На Mac и Windows, соглашения немного другие. Каталогом для инсталляции пакетов на этих платформах является корневой каталог (или подкаталог) Python, который специфичен инсталлированной версии Python. И это также (единственный) каталог, в котором ищутся файлы конфигурации (*.pth).

Поддиректории каталога стандартной библиотеки


Поскольку любой подкаталог каталога sys.path подразумевается возможным для использования в качестве пакета, можно легко войти в заблуждение относительно тех, которые и не предполагаются как таковые. Например, предположим, что имеется подкаталог, под именем tkinter, содержащий модуль Tkinter.py. Как следует писать -- import Tkinter или import tkinter.Tkinter? Если подкаталог tkinter находится в пути, то оба способа будут работать, но это создает ненужную неразбериху.

Установлено простое соглашение по именованию, которое должно ликвидировать эту неразбериху: не-пакетные каталоги должны иметь дефис в своем имени. В частности, все платформо-специфичные подкаталоги (sunos5, win, mac, и т.д.) были переименованы с префиксом "plat-". Подкаталоги, специфичные для необязательных компонентов Python, которые еще не были конвертированы в пакеты, переименованы с префиксом "lib-". Подкаталог dos_8x3 был переименован в dos-8x3. В следующей таблице приведены все переименованные директории:

Старое имяНовое имя
tkinterlib-tk
stdwinlib-stdwin
sharedmoduleslib-dynload
dos_8x3dos-8x3
aix3plat-aix3
aix4plat-aix4
freebsd2plat-freebsd2
genericplat-generic
irix5plat-irix5
irix6plat-irix6
linux1plat-linux1
linux2plat-linux2
next3plat-next3
sunos4plat-sunos4
sunos5plat-sunos5
winplat-win
testtest

Примечание, что подкаталог тестирования еще не переименован. Теперь это пакет. Для его вызова, используйте оператор вроде import test.autotest.

Другие вещи


XXX У меня еще не было времени обсудить следующие пункты:
  • Новая функция imp.
  • Будущие направления.
  • Будущие ihooks.
  • Будущая реорганизация пространств имен.
  • Что делать с ni? Блокировать его и принудительное использование старого ni?

    Изменения в ni


    Следующие особенности ni были сдублированны непосредственно. Игнорируйте эту секцию, если Вы в настоящее время не используете модуль ni и хотите перейти к использованию встроенной поддержки модулей.

    Выброшенный __domain__


    По умолчанию, когда подмодуль пакета A.B.C импортирует модуль X, ni будет искать A.B.C.X, A.B.X, A.X и X, в том же порядке. Раньше это определялось в пакете, с помощью переменной __domain__, которой можно было присваивать список имен пакетов для поиска. Данная особенность ликвидирована во встроенной поддержке пакетов. Вместо этого, всегда сперва ищется A.B.C.X, а затем X. (Это изменение для достижения "двух областей" удачно используется еще кое-где в Python, для решений, связанных с пространствами имен.)

    Выброшенный __


    При использовании ni, пакеты могли явно задавать "относительные" имена модулей, используя специальное имя "__" (два подчеркивания). К примеру, модули в пакете A.B.C могли ссылаться на модули, определенные в пакете A.B.K, через имя в виде __.__.K.модуль . Эта особенность была выброшена из-за ее ограниченного использования и неважной удобочитаемости.

    Несовместимая семантика для __init__


    При использовании ni, файл __init__.py в пакете (в случае его существования) был бы импортирован как стандартный подмодуль пакета. Встроенная же поддержка пакетов вместо этого загружает файл __init__.py в пространство имен пакета. Это означает, что если __init__.py в пакете A определяет имя x, то на него можно будет сослаться как A.x, без всякого дополнительного усилия. При использовании ni, __init__.py должен был бы содержать присваивание в виде __.x = x, для достижения такого же эффекта.

    Кроме того, новая поддержка пакетов требует, чтобы __init__ модуль существовал; во времена ni это было не обязательно. Это изменение введено в Python 1.5b1, и было задумано во избежание того, чтобы каталоги с обычными именами, вроде "string", непреднамеренно не прятали корректные модули, после присутствующие в пути поиска модулей.

    Пакеты, желающие быть обратно совместимыми с ni, могут проверять существует ли специальная переменная __ , напр.:

    # Определить функцию, видимую на уровне пакета
    def f(...): ...
    
    try:
        __
    except NameError:    # новая встроенная поддержка пакетов
        pass
    else:                # обратная совместимость для ni
        __.f = f
    

    [Содержание]


  • Реклама на InfoCity

    Яндекс цитирования



    Финансы: форекс для тебя








    1999-2009 © InfoCity.kiev.ua