Пакеты пространства имен Python в Python3

Тема пакетов пространства имен кажется немного запутанной для непосвященных, и это не помогает тем, что предыдущие версии Python реализовали это несколькими способами или что много Q & A на StackOverflow датировано. Я ищу решение в Python 3.5 или более поздней.

Сценарий:

Я занимаюсь реорганизацией кучки кода Python в модулях и подмодулях и работаю над тем, чтобы каждый из этих проектов работал независимо друг от друга, сидя в одном и том же пространстве имен.

В конечном итоге мы будем использовать внутренний PyPi-сервер, обслуживающий эти пакеты в нашей внутренней сети и не хотим путать их с внешними (общедоступными) пакетами PyPi.

Пример: У меня есть 2 модуля, и я хотел бы иметь возможность выполнить следующее:

from org.client.client1 import mod1
from org.common import config

Отраженные модули будут разделены как таковые:

Репозиторий 1:

org_client_client1_mod1/
  setup.py
  mod1/
    __init__.py
    somefile.py

Репозиторий 2:

org_common_config/
  setup.py
  config/
    __init__.py
    someotherfile.py

Мои репозитории Git уже настроены как org_client_client1_mod1 и org_common_config, поэтому мне просто нужно выполнить настройку на упаковке и __init__.py файлах, я полагаю.

Вопросы:

# 1

С __init__.py, какой из них я должен использовать (если есть)?:

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

Или:

import pkg_resources
pkg_resources.declare_namespace(__name__)

# 2

С setup.py мне все еще нужно добавить параметр namespace_modules, и если да, я бы использовал namespace_modules=['org.common'], или namespace_modules=['org', 'common']?

# 3

Могу ли я отказаться от всего вышеизложенного, просто каким-то образом это реализовать? Возможно, что-то более простое или более "питоновое"?

Ответ 1

Опоздал на вечеринку, но никогда не повредит помочь попутчикам по пути к пространству имен в Python!

# 1:

С init.py, какой из них мне следует использовать (если есть)?:

Зависит от того, что есть три способа сделать пакеты пространства имен, как указано здесь:

  1. Используйте собственные пакеты пространства имен. Этот тип пакета пространства имен определен в PEP 420 и доступен в Python 3.3 и более поздних версиях. Это рекомендуется, если пакеты в вашем пространстве имен только когда-либо должны поддерживать Python 3 и установку через pip.

  2. Используйте пакеты пространства имен в стиле pkgutil. Это рекомендуется для новых пакетов, которые должны поддерживать Python 2 и 3, и установки через установку pip и python setup.py.

  3. Используйте пакеты пространства имен в стиле pkg_resources. Этот метод рекомендуется, если вам нужна совместимость с пакетами, уже использующими этот метод, или если ваш пакет должен быть zip-safe.

Если вы используете # 2 (pkgutil-style) или # 3 (pkg_resources-style), то вам придется использовать соответствующий стиль для файлов __init__.py. Если вы используете собственные пространства имен, то в каталоге пространств имен нет __init__.py.

# 2:

С setup.py, мне все еще нужно добавить параметр namespace_modules, и если да, буду ли я использовать namespace_modules = ['org.common'] или namespace_modules = ['org', 'common']?

Если выбранный вами пакет пространства имен не является собственным стилем, то да, вам понадобится namespace_packages в вашем setup().

# 3:

Могу ли я отказаться от всего вышеперечисленного, просто реализовав это иначе? Возможно, что-то более простое или более "питоническое"?

Поскольку вы остановились на полной теме в python, кажется, вы знаете, что делаете, чего хотите, и определили, что создание пакета пространства имен Python - это способ сделать это. Это будет считаться питонским способом решения проблемы.


В дополнение к вашим вопросам вот несколько вещей, которые я обнаружил:

Я прочитал PEP420, Руководство по упаковке Python, и потратил много времени на понимание пакетов пространства имен, и в целом я понял, как это работает. Я прочитал пару ответов здесь, здесь, здесь, а также этот ответ на Кару. Пример здесь и по ссылке Git, опубликованной Робом.

Однако моя проблема была после того, как я создал свой пакет. Поскольку все инструкции и примеры кода явно перечисляли пакет в функции setuptools.setup(package=[]), мой код не удался. Мои подпакеты/каталоги не были включены. Углубившись вглубь, я обнаружил, что в setuptools есть функция find_namespace_package(), которая также помогает добавлять подпакеты

РЕДАКТИРОВАТЬ:

Ссылка на find_namespace_packages() (версия setuptools больше, чем 40.1.0): https://setuptools.readthedocs.io/en/latest/setuptools.html#find-namespace-packages

ОБНОВЛЕНИЕ (08/09/2019):

Чтобы завершить ответ, позвольте мне также привести пример с реструктуризацией.

Следующее решение предполагает использование Python 3. 3+, который поддерживает неявные пакеты пространства имен

Поскольку вы ищете решение для версии Python 3.5 или более поздней, давайте возьмем предоставленные примеры кода и уточним.

Давайте предположим следующее:

Имя пакета/имя пакета Python: org

Дистрибутивные пакеты: org_client, org_common

Python: 3.3+

setuptools: 40.1.0

Для вас сделать следующее

from org.client.client1 import mod1
from org.common import config

И ваши каталоги высшего уровня остаются прежними, а именно. org_client_client1_mod1 и org_common_config, вы можете изменить свою структуру следующим образом

Репозиторий 1:

org_client_client1_mod1/
  setup.py
  org/
    client/
      client1/
        __init__.py
        submod1/
          __init__.py
        mod1/
          __init__.py
          somefile.py
        file1.py

Обновлено setup.py

from setuptools import find_namespace_packages, setup
setup(
    name="org_client",
    ...
    packages=find_namespace_packages(), # Follows similar lookup as find_packages()
    ...
)

Хранилище 2:

org_common_config/
  setup.py
  org/
    common/
      __init__.py
      config/
        __init__.py
        someotherfile.py

Обновлено setup.py:

from setuptools import find_namespace_packages, setup
setup(
    name="org_common",
    ...
    packages=find_namespace_packages(), # Follows similar lookup as find_packages()
    ...
)

Для установки (используя pip):

(venv) $ pip3 install org_common_config/
(venv) $ pip3 install org_client_client1_mod1/

Обновленный список пипсов покажет следующее:

(venv) $ pip3 list
...
org_client
org_common
...

Но они не будут импортироваться, чтобы импортировать, вам нужно следовать указаниям org.client и org.common.

Чтобы понять почему, вы можете просмотреть здесь (при условии, что внутри venv):

(venv) $ cd venv/lib/python3.5/site-packages/
(venv) $ ls -l | grep org

Вы увидите, что там нет каталогов org_client или org_common, они интерпретируются как пакет пространства имен.

(venv) $ cd venv/lib/python3.5/site-packages/org/
(venv) $ ls -l
client/
common/
...

Ответ 2

Это сложная тема. Все - -, _ и __init__.py везде не совсем облегчают нам задачу.

Сначала я отвечу на ваши вопросы:

С __init__.py, какой из них я должен использовать (если есть)?

  • __init__.py может быть полностью пустым, он просто должен быть в правильном месте. А именно (каламбур) они должны быть в любом подпакете, содержащем код Python (кроме setup.py.). Следуйте этим правилам, и у вас все будет хорошо.

С setup.py, мне все еще нужно добавить параметр namespace_modules, и если да, буду ли я использовать namespace_modules=['org.common'] или namespace_modules=['org', 'common']?

  • Нету! Только name= и packages=. Однако обратите внимание на формат packages= arg по сравнению со структурой каталогов.
  • Вот формат package= arg: setup.py for namespace package
  • Вот соответствующая структура каталогов: example_package/a/<subpackage>

Могу ли я отказаться от всего вышеперечисленного, просто реализовав это иначе? Возможно, что-то более простое или более "питоническое"?

  • Если вы хотите иметь возможность устанавливать несколько функций по отдельности, но в одном пространстве имен верхнего уровня, вы на правильном пути.

Я потрачу оставшуюся часть этого ответа на повторную реализацию вашего пакета пространства имен в собственном формате:

Я помещу всю полезную документацию, которую смог найти, внизу поста.

К, так что я собираюсь предположить, что вы хотите нативные пакеты пространства имен. Сначала давайте посмотрим на текущую структуру ваших 2 репозиториев:

org_client_client1_mod1/
  setup.py
  mod1/
    __init__.py
    somefile.py

&

org_common_config/
  setup.py
  config/
    __init__.py
    someotherfile.py

Это было бы слишком просто !!!

Чтобы получить то, что вы хотите:

Мой мозг недостаточно эластичен, чтобы знать, можем ли мы пройти 3 уровня с пакетами пространства имен, но делать то, что вы хотите, вот что, я уверен, вы захотите сделать:

org-client/
  setup.py
  org/
    client/
      client1/
        __init__.py
        mod1/
          __init__.py
          somefile.py

&

org-common-but-also-note-this-name-doesnt-matter/
  setup.py
  org/
    common/
      __init__.py
      config/
        __init__.py
        someotherfile.py

По сути, ключ будет указывать правильное name= & packages= args для stuptools.setup() внутри каждого setup.py.

Это будут:

name='org_client',
...
packages=['org.client']

&

name='org_common'
...
packages['org.common']

соответственно.

Затем просто установите каждый из них с помощью pip install. внутри каждого верхнего уровня реж.

Установка первого даст вам доступ к модулю somefile.py, а установка второго даст вам доступ к someotherfile.py. Также вас не смущает попытка установить 2 пакета с именем org в одной среде.

Итак, самый полезный раздел документации: https://packaging.python.org/guides/packaging-namespace-packages/#packaging-namespace-packages

А потом вот как я на самом деле пришел к пониманию этого: https://github.com/pypa/sample-namespace-packages/tree/master/native