Импорт пакетов братьев и сестер
Я пробовал читать вопросы об импорте братьев и сестер и даже пакета документации, но я еще не нашел ответа.
со следующей структурой:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
как могут скрипты в examples
и tests
импорт каталогов из
api
модуль и запускаться из командной строки?
кроме того, я хотел бы избежать уродливого sys.path.insert
взломать для каждого файла. Конечно
это можно сделать на Python, верно?
9 ответов:
как уже говорилось в другом месте, ужасная правда заключается в том, что вам нужно делать уродливые хаки, чтобы разрешить импорт из модулей братьев и сестер или родительского пакета из
__main__
модуль. Этот вопрос подробно описан в PEP 366. PEP 3122 пытался обрабатывать импорт более рациональным способом, но Гвидо отклонил его один счетединственный вариант использования, похоже, запускает сценарии, которые происходят чтобы жить внутри каталога модуля, который я всегда видел как антимодель.
(здесь)
хотя, я использую этот шаблон на регулярной основе с
# Ugly hack to allow absolute import from the root folder # whatever its name is. Please forgive the heresy. if __name__ == "__main__" and __package__ is None: from sys import path from os.path import dirname as dir path.append(dir(path[0])) __package__ = "examples" import api
здесь
path[0]
это родительская папка вашего запущенного скрипта иdir(path[0])
ваша папка верхнего уровня.Я все еще не смог использовать относительный импорт с этим, хотя, но он позволяет абсолютный импорт с верхнего уровня (в вашем примере
api
родительская папка).
вот еще одна альтернатива, которую я вставляю в верхней части файлов Python в :
# Path hack. import sys, os sys.path.insert(0, os.path.abspath('..'))
вы не должны и не должны взломать
sys.path
если только это не необходимо и в данном случае это не так. Использование:import api.api_key # in tests, examples
запуск из каталога проекта:
python -m tests.test_one
.вы, вероятно, должны двигаться
tests
(если они являются unittests api) внутриapi
и работатьpython -m api.test
для выполнения всех тестов (при условии, что есть__main__.py
) илиpython -m api.test.test_one
для выполнения .вы также можете удалить
__init__.py
Сexamples
(это не пакет Python) и запустите примеры в виртуальное окружение, гдеapi
установлен, например,pip install -e .
в виртуальное окружение будет установить на местеapi
пакета, если у вас есть правильныйsetup.py
.
у меня еще нет понимания Pythonology, необходимого для того, чтобы увидеть предполагаемый способ обмена кодом между несвязанными проектами без взлома sibling/relative import. До этого дня, это мое решение. Ибо
examples
илиtests
импортировать материал из..\api
, это будет выглядеть так:import sys.path import os.path # Import from sibling directory ..\api sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..") import api.api import api.api_key
устал на sys.путь хаки?
много
sys.path.append
-хаки доступны, но я нашел альтернативный способ решения проблемы в руке:setuptools. Я не уверен, что есть крайние случаи, которые не работают хорошо с этим. Следующее тестируется с Python 3.6.5, (Anaconda, conda 4.5.1), Windows 10 машины.
настройка
отправной точкой является предоставленная вами файловая структура, завернутая в папку называется
myproject
.. └── myproject ├── api │ ├── api_key.py │ ├── api.py │ └── __init__.py ├── examples │ ├── example_one.py │ ├── example_two.py │ └── __init__.py ├── LICENCE.md ├── README.md └── tests ├── __init__.py └── test_one.py
я позову
.
корневая папка, и в моем примере она находится по адресуC:\tmp\test_imports\
.api.py
в качестве тестового случая, давайте использовать следующее ./api/api.py
def function_from_api(): return 'I am the return value from api.api!'
test_one.py
from api.api import function_from_api def test_function(): print(function_from_api()) if __name__ == '__main__': test_function()
попробуйте запустить test_one:
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py Traceback (most recent call last): File ".\myproject\tests\test_one.py", line 1, in <module> from api.api import function_from_api ModuleNotFoundError: No module named 'api'
также пытается относительный импорт не будет работать:
используя
from ..api.api import function_from_api
приведет вPS C:\tmp\test_imports> python .\myproject\tests\test_one.py Traceback (most recent call last): File ".\tests\test_one.py", line 1, in <module> from ..api.api import function_from_api ValueError: attempted relative import beyond top-level package
шаги
1) Сделайте a setup.py файл в корневой каталог
содержание
setup.py
будет*from setuptools import setup, find_packages setup(name='myproject', version='1.0', packages=find_packages())
2) использовать виртуальную среду
если вы знакомы с виртуальными средами, активируйте одну из них и перейдите к следующему шагу. использование виртуальных сред не абсолютно требуются, но они будут действительно помочь вам в в долгосрочной перспективе (когда у вас более 1 проекта продолжается..). Самые основные шаги (выполнить в корневой папке)
- создать виртуальный env
python -m venv venv
- активация виртуального окружения
. /venv/bin/activate
(Linux) или./venv/Scripts/activate
(Win)чтобы узнать больше об этом, просто Google out "python virtual env tutorial" или аналогичный. Вероятно, вам никогда не нужны никакие другие команды, кроме создания, активация и деактивация.
после того, как вы создали и активировали виртуальную среду, ваша консоль должна дать имя виртуальной среды в скобках
PS C:\tmp\test_imports> python -m venv venv PS C:\tmp\test_imports> .\venv\Scripts\activate (venv) PS C:\tmp\test_imports>
и ваше дерево папок должно выглядеть так**
. ├── myproject │ ├── api │ │ ├── api_key.py │ │ ├── api.py │ │ └── __init__.py │ ├── examples │ │ ├── example_one.py │ │ ├── example_two.py │ │ └── __init__.py │ ├── LICENCE.md │ ├── README.md │ └── tests │ ├── __init__.py │ └── test_one.py ├── setup.py └── venv ├── Include ├── Lib ├── pyvenv.cfg └── Scripts [87 entries exceeds filelimit, not opening dir]
3) pip установите свой проект в редактируемом состоянии
установите пакет верхнего уровня
myproject
используяpip
. Фокус в том, чтобы использовать-e
флаг при выполнении установки. Таким образом, он устанавливается в редактируемое состояние, и все изменения, внесенные в файлы .py, будут автоматически включены в установленный пакет.в корневом каталоге, выполните команду
pip install -e .
(обратите внимание на точку, оно означает "текущий каталог")вы также можете увидеть, что он установлен с помощью
pip freeze
(venv) PS C:\tmp\test_imports> pip install -e . Obtaining file:///C:/tmp/test_imports Installing collected packages: myproject Running setup.py develop for myproject Successfully installed myproject (venv) PS C:\tmp\test_imports> pip freeze myproject==1.0
4) Добавить
myproject.
в ваш импортобратите внимание, что вы должны добавить
myproject.
только в импорт, который не будет работать иначе. Импортозамещающий это работало безsetup.py
&pip install
совместимость по-прежнему работать нормально. См. пример ниже.
на всякий случай, если кто-то использует Pydev на Eclipse, вы можете добавить родительский путь брата (и, следовательно, Родительский модуль вызова) в качестве внешней папки библиотеки с помощью -2 - >Свойства и параметр Внешние Библиотеки в левом меню Pydev-PYTHONPATH. Затем вы можете импортировать из вашего брата, например
from sibling import some_class
.
для импорта пакетов братьев и сестер вы можете использовать либо вставить или добавить метод [sys.путь][2] модуль:
if __name__ == '__main__' and if __package__ is None: import sys from os import path sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) ) import api
это будет работать, если вы запускаете скрипты следующим образом:
python examples/example_one.py python tests/test_one.py
С другой стороны, вы также можете использовать относительный импорт:
if __name__ == '__main__' and if __package__ is not None: import ..api.api
в этом случае вам придется запустить свой скрипт с помощью -м аргументом' (обратите внимание, что, в этом случае, вы не должны дайте'.py' расширение):
python -m packageName.examples.example_one python -m packageName.tests.test_one
конечно, вы можете смешивать эти два подхода, так что ваш скрипт будет работать независимо от того, как это называется:
if __name__ == '__main__': if __package__ is None: import sys from os import path sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) ) import api else: import ..api.api
вам нужно посмотреть, как операторы импорта написаны в соответствующем коде. Если
examples/example_one.py
использует следующую инструкцию импорта:import api.api
...затем он ожидает, что корневой каталог проекта будет находиться в системном пути.
самый простой способ поддержать это без каких-либо хаков (как вы выразились) было бы запустить примеры из каталога верхнего уровня, например:
PYTHONPATH=$PYTHONPATH:. python examples/example_one.py
во-первых, вы должны избегать наличия файлов с тем же именем, что и сам модуль. Это может нарушить другие импортные поставки.
при импорте файла интерпретатор сначала проверяет текущий каталог, а затем выполняет поиск в глобальных каталогах.
внутри
examples
илиtests
вы можете по телефонам:from ..api import api