Как мне создать пакет пространства имен в Python?
-
16-09-2019 - |
Вопрос
В Python пакет namespace позволяет вам распространять код 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 могли определять модули в этом пространстве имен?
Решение
ТЛ;ДР:
На Python 3.3 вам не нужно ничего делать, просто не ставьте никаких __init__.py
в каталогах пакетов вашего пространства имен, и все будет работать.В версии до 3.3 выберите pkgutil.extend_path()
решение по pkg_resources.declare_namespace()
one, потому что он ориентирован на будущее и уже совместим с неявными пакетами пространства имен.
В Python 3.3 представлены неявные пакеты пространства имен, см. ПЭП 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__)
не был обновлен для включения пакетов неявного пространства имен.
Другие советы
Есть стандартный модуль, называемый пкгутил, с помощью которого вы можете "добавлять" модули к заданному пространству имен.
С помощью предоставленной вами структуры каталогов:
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__)
(* поскольку - если вы не укажете зависимость между ними - вы не знаете, какой из них будет распознан первым - см. БОДРОСТЬ ДУХА 420 для получения дополнительной информации)
В качестве Документация говорит:
Это добавит к пакету
__path__
все подкаталоги каталогов наsys.path
назван в честь упаковки.
С этого момента вы должны иметь возможность распространять эти два пакета независимо.
Этот раздел должен быть довольно понятен.
Короче говоря, поместите код пространства имен в __init__.py
, обновлять setup.py
объявить пространство имен, и вы можете идти.
Это старый вопрос, но кто-то недавно прокомментировал в моем блоге, что моя публикация о пакетах пространств имен все еще актуальна, поэтому я подумал, что дам ссылку на нее здесь, поскольку она дает практический пример того, как это сделать:
Это ссылка на эту статью для получения основной информации о том, что происходит:
http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package
Тот Самый __import__("pkg_resources").declare_namespace(__name__)
хитрость в том, что она в значительной степени управляет управлением плагинами в Пикантная паутина и пока, кажется, все получается.
У вас есть концепции пространства имен 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