Как создать пакет пространства имен в 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 могли определять модули в этом пространстве имен?


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чтобы объявить пространство имен, и вы можете идти.


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 и до сих пор , кажется, работает вне.


-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.