Как создать пакет пространства имен в 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_path
(и path1
, 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
, названные в честь пакета.
Отныне вы должны иметь возможность распределять эти два пакета независимо.
Ответ 3
Этот раздел должен быть довольно понятным.
Короче говоря, поместите код пространства имен в __init__.py
, обновите setup.py
, чтобы объявить пространство имен, и вы можете свободно идти.
Ответ 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