Путь импорта 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, которые не должны использоваться вместе.