Python: импорт модуля из другого каталога на том же уровне в иерархии проекта

87

Я видел всевозможные примеры и другие подобные вопросы, но не могу найти пример, который точно соответствует моему сценарию. Я чувствую себя полным болваном, задавая это, потому что существует так много похожих вопросов, но я просто не могу заставить это работать «правильно». Вот мой проект:

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |
        |------- Scripts/
        |           |
        |           |----- __init__.py
        |           |----- CreateUser.py
        |           |----- FindUser.py

Если я перенесу «CreateUser.py» в основной каталог user_management, я могу легко использовать: "import Modules.LDAPManager"для импорта LDAPManager.py --- это работает. Чего я не могу (что хочу), так это сохранить CreateUser.py в подпапке Scripts и импортировать LDAPManager.py. Я надеялся добиться этого, используя "import user_management.Modules.LDAPManager.py". Это не работает. Короче говоря, я могу заставить файлы Python легко заглядывать глубже в иерархию, но я не могу заставить скрипт Python ссылаться на один каталог и переходить вниз в другой.

Обратите внимание, что я могу решить свою проблему, используя:

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import Modules.LDAPManager as LDAPManager

Я слышал, что это плохая практика, и мне не нравится.

Файлы в скриптах предназначены для непосредственного выполнения ( нужен ли вообще init .py в скриптах?). Я читал, что в этом случае я должен запускать CreateUser.py с флагом -m. Я пробовал несколько вариантов этого и просто не могу заставить CreateUser.py распознавать LDAPManager.py.

CptSupermrkt
источник

Ответы:

66

Если я CreateUser.pyперейду в основной каталог user_management, я могу легко использовать: import Modules.LDAPManagerto import LDAPManager.py --- это работает.

Пожалуйста, не надо . Таким образом, LDAPManagerмодуль , используемый CreateUserбудет не быть таким же , как тот , импортированными с помощью другого импорта. Это может создать проблемы, если у вас есть какое-то глобальное состояние в модуле или во время травления / распаковки. Избегайте импорта, который работает только потому, что модуль находится в том же каталоге.

Когда у вас есть структура пакета, вы должны:

  • Использование относительного импорта, то есть если CreateUser.pyв Scripts/:

     from ..Modules import LDAPManager
    

    Обратите внимание , что это было (обратите внимание на прошедшее время) обескуражен PEP 8 только потому , что старые версии питона не поддерживают их очень хорошо, но эта проблема была решена лет назад. Тока версия PEP 8 делает предложить их в качестве приемлемой альтернативы абсолютного импорта. Они мне действительно нравятся внутри упаковки.

  • Используйте абсолютный импорт, используя полное имя пакета ( CreateUser.pyin Scripts/):

     from user_management.Modules import LDAPManager
    

Чтобы второй работал, пакет user_managementдолжен быть установлен внутри PYTHONPATH. Во время разработки вы можете настроить среду IDE так, чтобы это происходило, без необходимости вручную добавлять вызовы sys.path.appendкуда-либо.

Также мне кажется странным, что Scripts/это подпакет. Поскольку в реальной установке user_managementмодуль будет установлен site-packagesв lib/каталоге, который находится в каталоге (какой бы каталог ни использовался для установки библиотек в вашей ОС), а сценарии должны быть установлены в bin/каталоге (в зависимости от того, какой каталог содержит исполняемые файлы для вашей ОС).

На самом деле я считаю, Script/что не должно быть даже ниже user_management. Он должен быть на том же уровне user_management. Таким образом, вам не нужно использовать -m, но вам просто нужно убедиться, что пакет можно найти (это опять же вопрос настройки IDE, правильной установки пакета или использования PYTHONPATH=. python Scripts/CreateUser.pyдля запуска сценариев с правильным путем).


Таким образом, я бы использовал следующую иерархию :

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |

 Scripts/  (*not* a package)
        |  
        |----- CreateUser.py
        |----- FindUser.py

Затем код CreateUser.pyи FindUser.pyдолжен использовать абсолютный импорт для импорта модулей:

from user_management.Modules import LDAPManager

Во время установки убедитесь, что он user_managementпопадает PYTHONPATHв каталог, а сценарии находятся внутри каталога для исполняемых файлов, чтобы они могли найти модули. Во время разработки вы либо полагаетесь на конфигурацию IDE, либо запускаете CreateUser.pyдобавление Scripts/родительского каталога в PYTHONPATH(я имею в виду каталог, содержащий оба user_managementи Scripts):

PYTHONPATH=/the/parent/directory python Scripts/CreateUser.py

Или вы можете изменить PYTHONPATHглобально, чтобы вам не приходилось указывать это каждый раз. В ОС Unix (Linux, Mac OS X и т. Д.) Вы можете изменить один из сценариев оболочки, чтобы определить PYTHONPATHвнешнюю переменную, в Windows вам нужно изменить настройки переменных среды.


Добавление Я считаю, что если вы используете python2, лучше избегать неявного относительного импорта, указав:

from __future__ import absolute_import

в верхней части ваших модулей. Этот способ import X всегда означает импорт модуля верхнего уровняX и никогда не будет пытаться импортировать X.pyфайл, который находится в том же каталоге (если этого каталога нет в каталоге PYTHONPATH). Таким образом, единственный способ выполнить относительный импорт - использовать явный синтаксис ( from . import X), который лучше ( явный лучше, чем неявный ).

Это гарантирует, что вы никогда не будете использовать «фиктивный» неявный относительный импорт, поскольку это вызовет ImportErrorявный сигнал о том, что что-то не так. В противном случае вы можете использовать модуль, который не соответствует вашему мнению.

Бакуриу
источник
Если вы используете относительный импорт, вы должны выполнитьpython -m user_management.Scripts.CreateUser
Мононоке
14

Начиная с Python 2.5, вы можете использовать

from ..Modules import LDAPManager

Ведущий период поднимает вас на уровень иерархии выше.

См. Документацию Python по ссылкам внутри пакета для импорта.

Джонршарп
источник
3

В "корне" __init__.pyтоже можно сделать

import sys
sys.path.insert(1, '.')

что должно сделать оба модуля импортируемыми.

rdodev
источник