Я пытался прочитать вопросы об импорте родного брата и даже документацию к пакету , но пока не нашел ответа.
Со следующей структурой:
├── 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, верно?
python
packages
python-import
siblings
zachwill
источник
источник
sys.path
хаки и прочитать единственное актуальное решение, которое было опубликовано до сих пор (через 7 лет!).Ответы:
Семь лет спустя
С тех пор как я написал ответ ниже, модификация
sys.path
- все еще быстрый и грязный трюк, который хорошо работает для частных скриптов, но было несколько улучшенийsetup.cfg
для хранения метаданных)-m
флага и запуск в качестве пакета тоже работает (но будет немного неловко, если вы захотите преобразовать ваш рабочий каталог в устанавливаемый пакет).sys.path
вас.Так что это действительно зависит от того, что вы хотите сделать. В вашем случае, тем не менее, поскольку кажется, что ваша цель в какой-то момент состоит в том, чтобы сделать правильный пакет, установка через
pip -e
, вероятно, будет лучшим выбором, даже если он еще не идеален.Старый ответ
Как уже говорилось в другом месте, ужасная правда в том, что вы должны делать некрасивые хаки, чтобы разрешить импорт из модулей одного уровня или из родительского пакета из
__main__
модуля. Вопрос подробно описан в PEP 366 . PEP 3122 попытался более рационально справиться с импортом, но Гвидо отверг это из-за( здесь )
Хотя я использую этот шаблон на регулярной основе с
Вот
path[0]
родительская папка вашего запущенного скрипта и папкаdir(path[0])
верхнего уровня.Я до сих пор не смог использовать относительный импорт с этим, хотя, но он разрешает абсолютный импорт с верхнего уровня (в
api
родительской папке вашего примера ).источник
-m
формы или устанавливаете пакет (pip и virtualenv облегчают это)__package__ = "examples"
меня. Почему вы используете это? 2. В какой ситуации есть,__name__ == "__main__"
но__package__
нетNone
?__packages__
помогает, если вам нужен абсолютный путь, например,examples.api
для работы iirc (но это было давно с тех пор, как я это делал в последний раз), и проверка того, что пакет не является None, была в основном отказоустойчивой для странных ситуаций и защиты будущего.Устали от взлома sys.path?
Доступно множество
sys.path.append
хаков, но я нашел альтернативный способ решения проблемы.Резюме
packaged_stuff
)setup.py
сценарий создания, где вы используете setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Настроить
Отправной точкой является предоставленная вами файловая структура, помещенная в папку с именем
myproject
.Я назову
.
корневую папку, и в моем примере она расположена по адресуC:\tmp\test_imports\
.api.py
В качестве тестового примера давайте используем следующее ./api/api.py
test_one.py
Попробуйте запустить test_one:
Также попытка относительного импорта не сработает:
Использование
from ..api.api import function_from_api
приведет кмеры
Содержание для
setup.py
будет *Если вы знакомы с виртуальными средами, активируйте одну и перейдите к следующему шагу. Использование виртуальных сред не является абсолютно обязательным, но они действительно помогут вам в долгосрочной перспективе (когда у вас более 1 проекта ..). Самые основные шаги (запустить в корневой папке)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) или./venv/Scripts/activate
(Win)Чтобы узнать больше об этом, просто посмотрите в Google "Python Virtual Env Tutorial" или подобное. Возможно, вам никогда не понадобятся какие-либо другие команды, кроме создания, активации и деактивации.
После того, как вы создали и активировали виртуальную среду, ваша консоль должна дать имя виртуальной среды в скобках.
и ваше дерево папок должно выглядеть так **
Установите пакет верхнего уровня,
myproject
используяpip
. Хитрость заключается в использовании-e
флага при установке. Таким образом, он устанавливается в редактируемом состоянии, и все изменения, внесенные в файлы .py, будут автоматически включены в установленный пакет.В корневом каталоге запустите
pip install -e .
(обратите внимание на точку, это означает «текущий каталог»)Вы также можете увидеть, что он установлен с помощью
pip freeze
myproject.
в свой импортОбратите внимание, что вам придется добавлять
myproject.
только в импорт, который не будет работать в противном случае. Импорт, который работал безsetup.py
&pip install
будет работать, по-прежнему работает нормально. Смотрите пример ниже.Проверьте решение
Теперь давайте проверим решение, используя
api.py
определенные выше иtest_one.py
определенные ниже.test_one.py
запустить тест
* См. Документацию по setuptools для более подробных примеров setup.py.
** На самом деле вы можете поместить свою виртуальную среду в любое место на жестком диске.
источник
-e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myproject
Любая идея о том, как решить?ModuleNotFoundError
? Я установил «myproject» в virtualenv, выполнив следующие действия, и когда я вхожу в интерпретируемый сеанс и запускаю,import myproject
я получаюModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
показывает , что она есть, каталог является правильным, и оба verison изpip
иpython
проверяется , чтобы быть правильными.pip list
показывает пакеты, в то время какpip freeze
показывает странные имена, если установлен с флагом -eВот еще одна альтернатива, которую я вставляю поверх файлов Python в
tests
папке:источник
..
здесь речь идет о каталоге, из которого вы выполняете, а не о каталоге, содержащем этот файл test / example. Я выполняю из каталога проекта, и мне нужно было./
вместо этого. Надеюсь, это поможет кому-то еще.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerВам не нужно и не следует взламывать,
sys.path
если это не необходимо, и в этом случае это не так. Использование:Запуск из директории проекта:
python -m tests.test_one
.Вам, вероятно, следует переместиться
tests
(если это юнит-тесты API) внутрьapi
и запустить,python -m api.test
чтобы запустить все тесты (при условии, что они есть__main__.py
) илиpython -m api.test.test_one
запуститьtest_one
вместо этого.Вы также можете удалить
__init__.py
изexamples
(это не пакет Python) и запустить примеры в virtualenv, гдеapi
он установлен, например,pip install -e .
в virtualenv установитapi
пакет inplace , если у вас все правильноsetup.py
.источник
python -m api.test.test_one
из любого места, когда virtualenv активирован. Если вы не можете настроить PyCharm для запуска своих тестов, попробуйте задать новый вопрос переполнения стека (если вы не можете найти существующий вопрос по этой теме).У меня пока нет понимания Pythonology, необходимого для того, чтобы увидеть предполагаемый способ совместного использования кода между несвязанными проектами без одноуровневого / относительного взлома импорта. До этого дня это мое решение. Для
examples
илиtests
для импорта материала..\api
, это будет выглядеть так:источник
Для импорта братья пакет, вы можете использовать либо вкладыш или на добавление метод [sys.path] [2] модуль:
Это будет работать, если вы запускаете свои скрипты следующим образом:
С другой стороны, вы также можете использовать относительный импорт:
В этом случае вам нужно будет запустить скрипт с аргументом '-m' (обратите внимание, что в этом случае вы не должны давать расширение '.py' ):
Конечно, вы можете смешать два подхода, чтобы ваш скрипт работал независимо от того, как он называется:
источник
__file__
глобальной системы, поэтому мне пришлось использовать следующее:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Но сейчас она работает в любом каталогеTLDR
Этот метод не требует установки инструментов, хаков путей, дополнительных аргументов командной строки или указания верхнего уровня пакета в каждом отдельном файле вашего проекта.
Просто сделайте скрипт в родительском каталоге того, что вы называете своим,
__main__
и запустите все оттуда. Для дальнейшего объяснения продолжите чтение.объяснение
Этого можно достичь, не взламывая новый путь вместе, не добавляя аргументы командной строки или не добавляя код в каждую из ваших программ для распознавания ее родных элементов.
Причина, по которой, как я полагаю, упоминалась ранее, не работает, заключается в том, что вызываемые программы имеют свои
__name__
настройки__main__
. Когда это происходит, вызываемый скрипт принимает себя на верхнем уровне пакета и отказывается распознавать скрипты в каталогах одного уровня.Однако все, что находится на верхнем уровне каталога, все равно распознает все остальное на верхнем уровне. Это означает, что ЕДИНСТВЕННАЯ вещь, которую вы должны сделать, чтобы файлы в одноуровневых каталогах распознавали / использовали друг друга, - это вызывать их из скрипта в их родительском каталоге.
Доказательство концепции В каталоге со следующей структурой:
Main.py
содержит следующий код:sib1 / call.py содержит:
и sib2 / Callsib.py содержит:
Если вы воспроизведете этот пример, вы заметите, что вызов
Main.py
приведет к тому, что «Got Called» будет напечатан в соответствии с определением,sib2/callsib.py
даже если онsib2/callsib.py
был вызванsib1/call.py
. Однако, если кто-то должен был вызвать напрямуюsib1/call.py
(после внесения соответствующих изменений в импорт), он генерирует исключение. Даже если он работает при вызове сценария в родительском каталоге, он не будет работать, если он считает, что находится на верхнем уровне пакета.источник
Я сделал пример проекта, чтобы продемонстрировать, как я справился с этим, что действительно является еще одним взломом sys.path, как указано выше. Пример импорта Python Sibling , который опирается на:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Это кажется довольно эффективным, пока ваш рабочий каталог остается в корне проекта Python. Если кто-нибудь развернет это в реальной производственной среде, было бы здорово услышать, работает ли он там также.
источник
Вы должны посмотреть, как операторы импорта записаны в связанном коде. Если
examples/example_one.py
используется следующий оператор импорта:... тогда он ожидает, что корневой каталог проекта находится в системном пути.
Самый простой способ поддержать это без каких-либо взломов (как вы выразились) - запустить примеры из каталога верхнего уровня, например так:
источник
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. Я также получаю то же самое сimport api.api
.На тот случай, если кто-то, использующий Pydev в Eclipse, окажется здесь: вы можете добавить родительский путь брата (и, следовательно, родителя вызывающего модуля) в качестве папки внешней библиотеки, используя Project-> Properties и установив External Libraries в левом меню Pydev-PYTHONPATH . Затем вы можете импортировать из вашего брата, например
from sibling import some_class
.источник
Во-первых, вы должны избегать файлов с тем же именем, что и сам модуль. Это может сломать другой импорт.
Когда вы импортируете файл, сначала интерпретатор проверяет текущий каталог, а затем ищет глобальные каталоги.
Внутри
examples
илиtests
вы можете позвонить:источник
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
файл в каталог верхнего уровня. В противном случае Python не может рассматривать его как модуль__name__
является__main__
вместоpackage.module
, Python не может видеть его родительский пакет, так что.
указывает на ничто.