Импорт пакетов братьев и сестер


Я пробовал читать вопросы об импорте братьев и сестер и даже пакета документации, но я еще не нашел ответа.

со следующей структурой:

├── 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 67

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