Для чего полезен __path__?
Я никогда не замечал атрибут __path__
, который определяется на некоторых моих пакетах до сегодняшнего дня. Согласно документации:
Пакеты поддерживают еще одну специальную атрибут __path__
. Это инициализируется как список, содержащий имя каталога, в котором находится пакеты __init__.py
перед кодом в этом файле. Эта переменная может быть изменена; делать это влияет на будущие поиски модулей и подпакеты, содержащиеся в пакет.
Хотя эта функция не часто необходимо, его можно использовать для расширения набор модулей, найденных в пакете.
Может кто-нибудь объяснить мне, что именно это означает и почему я когда-нибудь захочу его использовать?
Ответы
Ответ 1
Обычно это используется с pkgutil, чтобы пакет был размещен на диске. Например, zope.interface и zope.schema являются отдельными дистрибутивами (zope
является "пакетом пространства имен" ). У вас может быть zope.interface, установленный в /usr/lib/python2.6/site-packages/zope/interface/
, тогда как вы используете zope.schema локально в /home/me/src/myproject/lib/python2.6/site-packages/zope/schema
.
Если вы поместите pkgutil.extend_path(__path__, __name__)
в /usr/lib/python2.6/site-packages/zope/__init__.py
, то оба zope.interface и zope.schema будут импортированы, потому что pkgutil будет иметь изменение __path__
- ['/usr/lib/python2.6/site-packages/zope', '/home/me/src/myproject/lib/python2.6/site-packages/zope']
.
pkg_resources.declare_namespace
(часть Setuptools) похожа на pkgutil.extend_path
, но больше знает об zips на пути.
Вручную изменение __path__
является необычным и, вероятно, не является необходимым, хотя полезно учитывать переменную при отладке проблем импорта с пакетами пространства имен.
Вы также можете использовать __path__
для monkeypatching, например, у меня есть обезьяна, переданная через distutils, создавая файл distutils/__init__.py
, который находится на раннем этапе sys.path
:
import os
stdlib_dir = os.path.dirname(os.__file__)
real_distutils_path = os.path.join(stdlib_dir, 'distutils')
__path__.append(real_distutils_path)
execfile(os.path.join(real_distutils_path, '__init__.py'))
# and then apply some monkeypatching here...
Ответ 2
Если вы измените __path__
, вы можете заставить интерпретатор искать в другом каталоге для модулей, принадлежащих этому пакету.
Это позволит вам, например, загружать разные версии одного и того же модуля в зависимости от условий выполнения. Вы можете сделать это, если хотите использовать разные реализации одинаковой функциональности на разных платформах.
Ответ 3
В дополнение к выбору различных версий модуля на основе условий выполнения, как говорит Syntactic, эта функциональность также позволит вам разбить ваш пакет на несколько частей/загрузки/установки, сохраняя при этом внешний вид единого логического пакета.
Рассмотрим следующее.
- У меня есть два пакета
mypkg
и _mypkg_foo
.
-
_mypkg_foo
содержит дополнительный модуль для mypkg
, foo.py
.
- как загружено и установлено,
mypkg
не содержит foo.py
.
mypkg
__init__.py
может сделать что-то вроде этого:
try:
import _mypkg_foo
__path__.append(os.path.abspath(os.path.dirname(_mypkg_foo.__file__)))
import mypkg.foo
except ImportError:
pass
Если кто-то установил пакет _mypkg_foo
, то mypkg.foo
доступен для них. Если они этого не сделали, его не существует.
Ответ 4
Особая ситуация, с которой я столкнулся, - это когда пакет становится настолько большим, что я хочу разбить его части на подкаталоги, не меняя никакого кода, который ссылается на него.
Например, у меня есть пакет под названием views
, который собирал ряд вспомогательных служебных функций, которые запутывались с главной целью пакета верхнего уровня. Мне удалось переместить эти вспомогательные функции в подкаталог utils
и добавить следующую строку в __init__.py
для пакета views
:
__path__.append(os.path.join(os.path.dirname(__file__), "utils"))
С этим изменением тоже views/__init_.py
, я мог бы запустить остальную часть программного обеспечения с новой структурой файла без каких-либо дальнейших изменений в файлах.
(Я попытался сделать что-то подобное с операторами import
в файле views/__init__.py
, но модули подпакетов все еще не были видны через импорт пакета view
- я не совсем уверен, что если я "Там что-то не хватает, комментарии об этом приветствуются!)
(Этот ответ основан на установке Python 2.7)