Как мне структурировать мой проект 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

Цели:

  1. Иметь организованную структуру проекта
  2. Уметь независимо запускать каждый файл.py при необходимости
  3. Иметь возможность ссылаться на / импортировать оба родственных и двоюродных модуля
  4. Сохраняйте все операторы 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 является проблемой:

  1. Иметь возможность независимо запускать каждый файл.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 и вы получите две отдельные копии одного и того же модуля).

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