Путь импорта python: пакеты с одинаковыми именами в разных папках

Я разрабатываю несколько проектов Python для нескольких клиентов одновременно. Упрощенная версия структуры папок моего проекта выглядит примерно так:

/path/
  to/
    projects/
      cust1/
        proj1/
          pack1/
            __init__.py
            mod1.py
        proj2/
          pack2/
            __init__.py
            mod2.py
      cust2/
        proj3/
          pack3/
            __init__.py
            mod3.py

Когда я, например, хочу использовать функциональность из proj1 Я расширяю sys.path от /path/to/projects/cust1/proj1 (например, установив PYTHONPATH или добавление .pth подать в site_packages папка или даже изменение sys.path напрямую), а затем импортируйте модуль следующим образом:

>>> from pack1.mod1 import something

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

/path/
  to/
    projects/
      cust3/
        proj4/
          pack1/    <-- same package name as in cust1/proj1 above
            __init__.py
            mod4.py

Если бы я сейчас просто продлил sys.path от /path/to/projects/cust3/proj4 Я все еще могу импортировать из proj1, но не из proj4:

>>> from pack1.mod1 import something
>>> from pack1.mod4 import something_else
ImportError: No module named mod4

Я думаю, что причина, по которой второй импорт завершается неудачей, заключается в том, что Python ищет только первую папку в sys.path где он находит pack1 пакет и сдается, если он не находит mod4 модуль там. Я спрашивал об этом в предыдущем вопросе, см. Импорт модулей Python с тем же именем, но внутренние детали до сих пор мне неясны.

В любом случае, очевидное решение - добавить еще один уровень квалификации пространства имен, превратив каталоги проектов в суперпакеты: Добавить __init__.py файлы к каждому proj* папку и удалите эти папки из строк, по которым sys.path расширен, например,

$ export PYTHONPATH=/path/to/projects/cust1:/path/to/projects/cust3
$ touch /path/to/projects/cust1/proj1/__init__.py
$ touch /path/to/projects/cust3/proj4/__init__.py
$ python
>>> from proj1.pack1.mod1 import something
>>> from proj4.pack1.mod4 import something_else

Сейчас я сталкиваюсь с ситуацией, когда разные проекты для разных клиентов имеют одинаковое имя, например

/path/
  to/
    projects/
      cust3/
        proj1/    <-- same project name as for cust1 above
          __init__.py
          pack4/
            __init__.py
            mod4.py

Попытка импортировать из mod4 больше не работает по той же причине, что и раньше:

>>> from proj1.pack4.mod4 import yet_something_else
ImportError: No module named pack4.mod4

Следуя тому же подходу, который решал эту проблему ранее, я бы добавил еще один слой пакета / пространства имен и превратил бы папки клиентов в супер-супер-пакеты.

Однако это противоречит другим требованиям, предъявляемым к структуре папок моего проекта, например

  • Структура разработки / выпуска для поддержки нескольких строк кода
  • другие виды исходного кода, такие как JavaScript, SQL и т. д.
  • другие файлы, чем исходные файлы, например, документы или данные.

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

/path/
  to/
    projects/
      cust1/
        proj1/
          Development/
            code/
              javascript/
                ...
              python/
                pack1/
                  __init__.py
                  mod1.py
            doc/
              ...
          Release/
            ...
        proj2/
          Development/
            code/
              python/
                pack2/
                  __init__.py
                  mod2.py

Я не понимаю, как я могу удовлетворить требования, предъявляемые интерпретатором Python к структуре папок и тем, которые у меня есть одновременно. Может быть, я мог бы создать дополнительную структуру папок с некоторыми символическими ссылками и использовать это в sys.path Но, глядя на усилие, которое я уже делаю, у меня возникает ощущение, что с моим подходом что-то в корне не так. Что касается сводки, мне также трудно поверить, что python действительно ограничивает меня в выборе имен папок с исходным кодом, как это происходит в изображенном случае.

Как я могу настроить папки моего проекта и sys.path так что я могу импортировать из всех проектов в согласованном порядке, если есть проект и пакеты с одинаковыми именами?

3 ответа

Решение

Это решение моей проблемы, хотя поначалу оно может быть неочевидным.

В своих проектах я теперь ввел соглашение об одном пространстве имен для каждого клиента. В каждой папке клиента (cust1, cust2и т. д.), есть __init__.py файл с этим кодом:

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

Все остальные __init__.py файлы в моих пакетах пусты (в основном потому, что у меня еще не было времени выяснить, что с ними делать).

Как объяснено здесь, extend_path удостоверится, что Python знает о наличии более одного подпакета в пакете, физически расположенном в другом месте, и, насколько я понимаю, интерпретатор не прекращает поиск после того, как ему не удается найти модуль по первому пути пакета, с которым он сталкивается в sys.path, но ищет все пути в __path__,

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

from cust1.proj1.pack1.mod1 import something
from cust3.proj4.pack1.mod4 import something_else
from cust3.proj1.pack4.mod4 import yet_something_else

С другой стороны, мне пришлось создать еще более глубокую структуру папок проекта:

/path/
  to/
    projects/
      cust1/
        proj1/
          Development/
            code/
              python/
                cust1/
                  __init__.py   <--- contains code as described above
                  proj1/
                    __init__.py <--- empty
                    pack1/
                    __init__.py <--- empty
                    mod1.py

но это кажется очень приемлемым для меня, особенно с учетом того, как мало усилий мне нужно приложить для поддержания этого соглашения. sys.path продлен на /path/to/projects/cust1/proj1/Development/code/python для этого проекта.

На заметке я заметил, что из всех __init__.py файлы для одного и того же клиента, который в пути, который появляется первым в sys.path выполняется, независимо от того, из какого проекта я импортирую что-либо.

Вы должны использовать превосходные инструменты virtualenv и virtualenvwrapper.

Что произойдет, если вы случайно импортируете код из одного клиента / проекта в другого и не заметите? Когда вы доставите это почти наверняка потерпит неудачу. Я бы принял соглашение о настройке PYTHONPATH для одного проекта за раз, и не пытался бы, чтобы все, что вы когда-либо писали, импортировалось за один раз.

Вы можете использовать сценарий-оболочку для каждого проекта, чтобы установить PYTHONPATH и запустить python, или использовать сценарии для переключения сред при переключении проектов.

Конечно, у некоторых проектов есть зависимости от других проектов (тех библиотек, которые вы упомянули), но если вы хотите, чтобы заказчик мог импортировать несколько проектов одновременно, вы должны принять меры к тому, чтобы имена не конфликтовали. Вы можете иметь эту проблему, только если у вас есть несколько проектов на PYTHONPATH, которые не должны использоваться вместе.

Другие вопросы по тегам