Как создать пакет пространства имен в Python?

144

В Python пакет пространства имен позволяет распределять код Python между несколькими проектами. Это полезно, если вы хотите выпустить связанные библиотеки как отдельные загрузки. Например, с каталогами Package-1и Package-2в PYTHONPATH,

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

конечный пользователь может import namespace.module1и import namespace.module2.

Как лучше всего определить пакет пространства имен, чтобы более одного продукта Python могли определять модули в этом пространстве имен?

Joeforker
источник
6
Мне кажется, что module1 и module2 на самом деле являются подпакетами, а не модулями. Насколько я понимаю, модуль - это в основном один файл. Может быть, имена subpkg1 и subpkg2 будут иметь больше смысла?
Алан

Ответы:

83

TL; DR:

В Python 3.3 вам не нужно ничего делать, просто не помещайте ничего __init__.pyв каталоги пакетов вашего пространства имен, и это будет работать. В версиях до 3.3 выберите pkgutil.extend_path()решение вместо pkg_resources.declare_namespace()одного, потому что оно ориентировано на будущее и уже совместимо с пакетами неявных пространств имен.


Python 3.3 представляет пакеты неявного пространства имен, см. PEP 420 .

Это означает, что теперь есть три типа объектов, которые могут быть созданы import foo:

  • Модуль, представленный foo.pyфайлом
  • Обычный пакет, представленный каталогом, fooсодержащим __init__.pyфайл
  • Пакет пространства имен, представленный одним или несколькими каталогами fooбез __init__.pyфайлов.

Пакеты тоже являются модулями, но здесь я имею в виду «не пакетный модуль», когда говорю «модуль».

Сначала sys.pathвыполняется поиск модуля или обычного пакета. В случае успеха поиск прекращается, создается и инициализируется модуль или пакет. Если он не нашел ни модуля, ни обычного пакета, но нашел хотя бы один каталог, он создает и инициализирует пакет пространства имен.

Модули и обычные пакеты __file__установили в .pyфайл, из которого они были созданы. Обычные пакеты и пакеты пространства имен __path__установлены в каталог или каталоги, из которых они были созданы.

Когда вы это сделаете import foo.bar, указанный выше поиск выполняется сначала для foo, а затем, если пакет был найден, поиск barвыполняется с foo.__path__использованием пути поиска вместо sys.path. Если foo.barобнаруживается, fooи foo.barсоздаются и инициализируются.

Так как же смешиваются обычные пакеты и пакеты пространства имен? Обычно это не так, но старый pkgutilметод пакета явного пространства имен был расширен за счет включения неявных пакетов пространства имен.

Если у вас есть существующий обычный пакет, который выглядит примерно __init__.pyтак:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

... унаследованное поведение заключается в добавлении любых других обычных пакетов по искомому пути к его __path__. Но в Python 3.3 он также добавляет пакеты пространства имен.

Таким образом, у вас может быть следующая структура каталогов:

├── path1
│   └── package
│       ├── __init__.py
│       └── foo.py
├── path2
│   └── package
│       └── bar.py
└── path3
    └── package
        ├── __init__.py
        └── baz.py

... и пока у двух __init__.pyесть extend_pathстроки (и path1, path2и path3в вашем sys.path) import package.foo, import package.barи import package.bazвсе будет работать.

pkg_resources.declare_namespace(__name__) не был обновлен для включения неявных пакетов пространства имен.

клацать
источник
2
А как насчет инструментов настройки? Должен ли я использовать эту namespace_packagesопцию? А в __import__('pkg_resources').declare_namespace(__name__)чем дело?
kawing-chiu
3
Должен ли я добавить namespace_packages=['package']в setup.py?
Laurent LAPORTE
1
@clacke: С namespace_packages=['package'], setup.py добавит namespace_packages.txtв EGG-INFO. Все еще не знаю ударов…
Лоран ЛАПОРТ
1
@ kawing-chiu Преимущество pkg_resources.declare_namespaceOver в pkgutil.extend_pathтом, что он будет продолжать отслеживать sys.path. Таким образом, если новый элемент добавляется sys.pathпосле первой загрузки пакета в пространстве имен, тогда пакеты в пространстве имен в этом новом элементе пути по-прежнему могут быть загружены. (Преимущество использования __import__('pkg_resources')over в import pkg_resourcesтом, что вас не pkg_resourcesвыставляют на my_namespace_pkg.pkg_resources
Артур Такка
1
@clacke Это не работает (но имеет тот же эффект, что и если бы это было). Он поддерживает глобальный список всех пространств имен пакетов, созданных с помощью этой функции, и наблюдает sys.path. При sys.pathизменении он проверяет, влияет ли это на __path__какое-либо пространство имен, и если да, то обновляет эти __path__свойства.
Артур Такка,
81

Существует стандартный модуль pkgutil , с помощью которого вы можете «добавлять» модули в заданное пространство имен.

С предоставленной вами структурой каталогов:

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

Вы должны поместить эти две строки в обе Package-1/namespace/__init__.pyи Package-2/namespace/__init__.py(*):

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

(* поскольку - если вы не укажете зависимость между ними - вы не знаете, какой из них будет распознан первым - см. PEP 420 для получения дополнительной информации)

Как говорится в документации :

Это добавит к пакету __path__все подкаталоги каталогов, sys.pathназванных в честь пакета.

С этого момента вы сможете распространять эти два пакета независимо.

Майк Хордеки
источник
17
Каковы плюсы и минусы использования этого по сравнению с import __ ('pkg_resources'). Declare_namespace (__ name )?
joeforker
14
Во-первых, __import__в данном случае это считается плохим стилем, поскольку его можно легко заменить простым оператором импорта. Более того, pkg_resources - нестандартная библиотека. Он поставляется с инструментами настройки, так что это не проблема. Быстрый поиск в Google показывает, что pkgutil был представлен в версии 2.5, а pkg_resources предшествует ему. Тем не менее, pkgutil - это официально признанное решение. Фактически включение pkg_resources было отклонено в PEP 365.
Майк Хордеки
3
Цитата из PEP 382 : Текущий императивный подход к пакетам пространства имен привел к появлению нескольких несовместимых механизмов для предоставления пакетов пространства имен. Например, pkgutil поддерживает файлы * .pkg; setuptools - нет. Аналогичным образом, setuptools поддерживает проверку zip-файлов и поддерживает добавление частей в свою переменную _namespace_packages, тогда как pkgutil этого не делает.
Дрейк Гуан,
7
Разве эти две строки нельзя поместить в оба файла: Package-1/namespace/__init__.py и при Package-2/namespace/__init__.py условии, что мы не знаем, какой каталог пакета указан первым?
Була
3
@ChristofferKarlsson: да, в том-то и дело, ничего страшного, если вы знаете, какой из них первый, но реальный вопрос в том, можете ли вы гарантировать, что он будет первым в любой ситуации, то есть для других пользователей?
Bula
5

Этот раздел не требует пояснений.

Короче говоря, вставьте код пространства имен __init__.py, обновите, setup.pyчтобы объявить пространство имен, и вы можете идти.

iElectric
источник
10
Вы всегда должны цитировать соответствующую часть ссылки, на случай, если соответствующая ссылка не работает.
Tinned_Tuna
2

Это старый вопрос, но кто-то недавно прокомментировал в моем блоге, что моя публикация о пакетах пространств имен все еще актуальна, поэтому подумал, что я дам ссылку на него здесь, поскольку он дает практический пример того, как это сделать:

https://web.archive.org/web/20150425043954/http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb

Это ссылки на эту статью, чтобы понять, что происходит:

http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package

__import__("pkg_resources").declare_namespace(__name__)Хитрость заключается в значительной степени приводит в действие управление плагинами в TiddlyWeb и до сих пор , кажется, работает вне.

cdent
источник
-9

У вас есть концепции пространства имен Python задом наперед, в python невозможно помещать пакеты в модули. Пакеты содержат модули, а не наоборот.

Пакет Python - это просто папка, содержащая __init__.pyфайл. Модуль - это любой другой файл в пакете (или непосредственно в нем PYTHONPATH), имеющий .pyрасширение. Итак, в вашем примере у вас есть два пакета, но не определены модули. Если вы считаете, что пакет - это папка файловой системы, а модуль - это файл, тогда вы поймете, почему пакеты содержат модули, а не наоборот.

Итак, в вашем примере, предполагая, что Package-1 и Package-2 являются папками в файловой системе, которые вы поместили на путь Python, вы можете иметь следующее:

Package-1/
  namespace/
  __init__.py
  module1.py
Package-2/
  namespace/
  __init__.py
  module2.py

Теперь у вас есть один пакет namespaceс двумя модулями module1и module2. и если у вас нет веской причины, вам, вероятно, следует поместить модули в папку и иметь только их на пути python, как показано ниже:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Тендаи Мавуше
источник
Я говорю о таких вещах, как zope.xнесколько связанных пакетов, которые выпускаются как отдельные загрузки.
joeforker
Хорошо, но какого эффекта вы пытаетесь достичь? Если все папки, содержащие связанные пакеты, находятся на PYTHONPATH, интерпретатор Python найдет их для вас без дополнительных усилий с вашей стороны.
Тендаи Мавуше
5
Если вы добавите в PYTHONPATH и Package-1, и Package-2, Python будет видеть только Package-1 / namespace /.
Søren Løvborg