Как мне структурировать мой проект Python, чтобы разрешить импорт названных модулей из подкаталогов
Это моя структура каталогов:
Projects
+ Project_1
+ Project_2
- Project_3
- Lib1
__init__.py # empty
moduleA.py
- Tests
__init__.py # empty
foo_tests.py
bar_tests.py
setpath.py
__init__.py # empty
foo.py
bar.py
Цели:
- Иметь организованную структуру проекта
- Уметь независимо запускать каждый файл.py при необходимости
- Иметь возможность ссылаться на / импортировать оба родственных и двоюродных модуля
- Сохраняйте все операторы import/from в начале каждого файла.
Я достиг #1, используя вышеупомянутую структуру
В основном я достиг 2, 3 и 4, выполнив следующее (как рекомендовано в этом превосходном руководстве)
В любой пакет, которому требуется доступ к родительским или двоюродным модулям (например, каталог Tests выше), я включаю файл с именем setpath.py, который имеет следующий код:
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('...'))
Затем в каждом модуле, которому требуется доступ для родителей / двоюродных братьев, например, foo_tests.py, я могу написать хороший чистый список импорта, например:
import setpath # Annoyingly, PyCharm warns me that this is an unused import statement
import foo.py
Внутри setpath.py вторая и третья вставки не являются строго необходимыми для этого примера, но включены в качестве шага устранения неполадок.
Моя проблема в том, что это работает только для импорта, который ссылается на имя модуля напрямую, а не для импорта, который ссылается на пакет. Например, внутри bar_tests.py ни один из двух приведенных ниже операторов не работает при непосредственном запуске bar_tests.py.
import setpath
import Project_3.foo.py # Error
from Project_3 import foo # Error
Я получаю ошибку "ImportError: Нет модуля с именем" Project_3 "".
Странно то, что я могу запустить файл прямо из PyCharm, и он отлично работает. Я знаю, что PyCharm делает закулисную магию с Python Path
переменная, чтобы все работало, но я не могу понять, что это такое. Поскольку PyCharm просто запускает python.exe и устанавливает некоторые переменные окружения, должна быть возможность клонировать это поведение из самого скрипта Python.
По причинам, не совсем уместным к этому вопросу, я должен ссылаться bar
с использованием Project_3
Классификатор.
Я открыт для любого решения, которое достигнет вышеупомянутого, все еще встречая мои предыдущие цели. Я также открыт для альтернативной структуры каталогов, если есть такая, которая работает лучше. Я прочитал документацию Python по импорту и пакетам, но все еще в растерянности. Я думаю, что одним из возможных путей может быть ручная настройка __path__
переменная, но я не уверен, какой из них нужно изменить или на что его установить.
2 ответа
Эти типы вопросов считаются "в первую очередь основанными на мнении", поэтому позвольте мне поделиться своим мнением о том, как я это сделаю.
Во-первых, "иметь возможность независимо запускать каждый файл.py при необходимости": либо файл является модулем, поэтому его не следует вызывать напрямую, либо он является автономно исполняемым, затем он должен импортировать свои зависимости, начиная с верхнего уровня (вы можете избежать это в коде или, скорее, переместить его в обычное место, используя setup.py entry_points, но тогда ваш бывший исполняемый файл эффективно преобразуется в модуль). И да, это одна из слабых сторон модели модулей Python, которая вызывает недоразумения.
Во-вторых, используйте virtualenv (или venv в Python3) и поместите каждый ваш Project_x в отдельный. Таким образом, имя проекта не будет частью пути модуля Python.
В-третьих, предоставленная вами ссылка упоминает setup.py - вы можете использовать ее. Поместите свой код в Project_x/src/mylib1, создайте src/mylib1/setup.py и, наконец, свои модули в src/mylib1/mylib1/module.py. Затем вы можете установить свой код с помощью pip, как любой другой пакет (или с помощью pip -e, чтобы вы могли работать с кодом напрямую, не переустанавливая его, хотя, к сожалению, он имеет некоторые ограничения).
И, наконец, как вы уже подтвердили в комментарии;). Проблема с вашей текущей моделью заключалась в том, что в sys.path.insert(0, os.path.abspath('...'))
вы по ошибке использовали нотацию модуля Python, которая неверна для системных путей и должна быть заменена на '../..'
работать как положено.
Я думаю, что ваши цели не разумны. В частности, цель № 2 является проблемой:
- Иметь возможность независимо запускать каждый файл.py при необходимости
Это не работает для модулей в пакете. По крайней мере, если вы работаете .py
файлы наивно (например, с python foo_tests.py
в командной строке). Когда вы запускаете файлы таким образом, Python не может определить, где должна начинаться иерархия пакетов.
Есть две альтернативы, которые могут работать. Первый вариант - запустить ваши скрипты из папки верхнего уровня (например, projects
) с использованием -m
установите флажок для интерпретатора, чтобы указать ему пунктирный путь к главному модулю, и используйте явный относительный импорт, чтобы получить модули братьев и сестер. Так что вместо того, чтобы бежать python foo_tests.py
прямо, беги python -m project_3.tests.foo_tests
от projects
папка (или python -m tests.foo_tests
изнутри project_3
возможно) и иметь foo_tests.py
использование from .. import foo
,
Другой (менее удачный) вариант - добавить папку верхнего уровня в путь поиска модуля установки Python в масштабе всей системы (например, добавить projects
папка в PYTHON_PATH
переменная окружения), а затем используйте абсолютный импорт для всех ваших модулей (например, import project3.foo
). Это эффективно то, что ваш setpath
модуль делает, но делать это в масштабе всей системы как часть конфигурации вашей системы, а не во время выполнения, это намного чище. Это также позволяет избежать нескольких имен, которые setpath
позволит вам использовать для импорта модуля (например, попробуйте import foo_tests, tests.foo_tests
и вы получите две отдельные копии одного и того же модуля).