Как структурировать проект 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, выполнив следующие действия (как рекомендовано это отличное руководство)

в любом пакете, который должен получить доступ к родительским или двоюродным модулям (например, каталог тестов выше), я включаю файл с именем 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 doc по импорту и пакеты, но я все еще в недоумении. Я думаю, что один из возможных путей может вручную установить __path__ переменная, но я не уверен, какой из них нужно изменить или что его установить.

2 ответов


эти типы вопросов квалифицируются как" в первую очередь основанные на мнении", поэтому позвольте мне поделиться своим мнением о том, как я это сделаю.

первый "сможет самостоятельно выполнить каждый .пы файл в случае необходимости": файл представляет собой модуль, поэтому он не должен быть вызван непосредственно, или это исполняемый файл, то он должен импортировать зависимостей, начиная от верхнего уровня (вы можете избежать этого в коде, точнее перенести ее в общие места, с помощью 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 флаг интерпретатору, чтобы дать ему пунктирный путь к основному модулю и использовать явный относительный импорт для получения модулей sibling и cousin. Так что вместо бега python foo_tests.py непосредственно, run python -m project_3.tests.foo_tests С (или python -m tests.foo_tests внутри project_3 возможно), а у нас есть foo_tests.py использовать from .. import foo.

другой (менее хороший) вариант-Добавить папку верхнего уровня к пути поиска модуля вашей установки Python на общесистемной основе (например, добавьте до PYTHON_PATH переменная окружения), а затем использовать абсолютный импорт для всех ваших модулей (например,import project3.foo). Это эффективно то, что ваш setpath модуль делает, но делает его общесистемным как часть конфигурации вашей системы, а не во время выполнения, он намного чище. Он также избегает нескольких имен, которые setpath позволит вам использовать для импорта модуля (например, try import foo_tests, tests.foo_tests и вы получите два отдельные копии того же модуля).