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

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

Ответ 1

TL; ДР:

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


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_pathpath1, path2 и path3 находятся в вашем sys.path) import package.foo, import package.bar и import package.baz все будет работать.

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

Ответ 2

Там есть стандартный модуль, называемый 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, названные в честь пакета.

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

Ответ 4

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

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

Ответ 5

У вас есть концепции пространства имен 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