Импортировать локальную функцию из модуля, размещенного в другом каталоге с относительным импортом в ноутбуке jupyter, используя python3
У меня есть структура каталогов, аналогичная следующей
meta_project
project1
__init__.py
lib
module.py
__init__.py
notebook_folder
notebook.jpynb
При работе в notebook.jpynb
, если я попытаюсь использовать относительный импорт для доступа к функции function()
в module.py
с помощью:
from ..project1.lib.module import function
Я получаю следующую ошибку
SystemError Traceback (most recent call last)
<ipython-input-7-6393744d93ab> in <module>()
----> 1 from ..project1.lib.module import function
SystemError: Parent module '' not loaded, cannot perform relative import
Есть ли способ заставить это работать, используя относительный импорт?
Примечание. Сервер ноутбука создается на уровне каталога meta_project
, поэтому он должен иметь доступ к информации в этих файлах.
Обратите также внимание, что, по крайней мере, как первоначально предполагалось, project1
не рассматривался как модуль и, следовательно, не имеет файла __init__.py
, это просто предназначалось как каталог файловой системы. Если решение проблемы требует рассмотрения его как модуля и включает в себя файл __init__.py
(даже пустой), это нормально, но этого недостаточно для решения проблемы.
Я разделяю этот каталог между машинами и относительным импортом, позволяя мне использовать один и тот же код везде, и я часто использую ноутбуки для быстрого прототипирования, поэтому предложения, которые предполагают совместное использование абсолютных путей, вряд ли будут полезны.
Изменить: это в отличие от Относительный импорт в Python 3, в котором говорится об относительном импорте в Python 3 в целом и, в частности, о запуске script изнутри каталог пакетов. Это связано с работой в ноутбуке jupyter, который пытается вызвать функцию в локальном модуле в другом каталоге, который имеет как разные общие, так и конкретные аспекты.
Ответы
Ответ 1
У меня был почти такой же пример, как вы в этом ноутбуке, где я хотел проиллюстрировать использование смежной функции модуля сухим способом.
Мое решение состояло в том, чтобы сообщить Python об этом пути импорта дополнительного модуля, добавив в блокнот такой сниппет:
import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
sys.path.append(module_path)
Это позволяет вам импортировать нужную функцию из иерархии модуля:
from project1.lib.module import function
# use the function normally
function(...)
Обратите внимание, что необходимо добавить пустые __init__.py
файлы в project1 и lib/folders, если у вас их уже нет.
Ответ 2
Пришел сюда в поисках лучших практик абстрагирования кода для субмодулей при работе в Блокнотах. Я не уверен, что есть лучшая практика. Я предлагал это.
Иерархия проекта как таковая:
├── ipynb
│ ├── 20170609-Examine_Database_Requirements.ipynb
│ └── 20170609-Initial_Database_Connection.ipynb
└── lib
├── __init__.py
└── postgres.py
И из 20170609-Initial_Database_Connection.ipynb
:
In [1]: cd ..
In [2]: from lib.postgres import database_connection
Это работает, потому что по умолчанию блокнот Jupyter может анализировать команду cd
. Обратите внимание, что это не использует магию Python Notebook. Это просто работает без предоплаты %bash
.
Учитывая, что 99 раз из 100 я работаю в Docker, используя один из образов Project Jupyter Docker, следующая модификация идемпотентна
In [1]: cd /home/jovyan
In [2]: from lib.postgres import database_connection
Ответ 3
До сих пор принятый ответ работал лучше всего для меня. Однако меня всегда беспокоило то, что существует вероятный сценарий, когда я мог бы преобразовать каталог notebooks
в подкаталоги, требуя замены module_path
в каждой записной книжке. Я решил добавить файл python в каждый каталог ноутбука, чтобы импортировать необходимые модули.
Таким образом, имея следующую структуру проекта:
project
|__notebooks
|__explore
|__ notebook1.ipynb
|__ notebook2.ipynb
|__ project_path.py
|__ explain
|__notebook1.ipynb
|__project_path.py
|__lib
|__ __init__.py
|__ module.py
Я добавил файл project_path.py
в каждый подкаталог ноутбука (notebooks/explore
и notebooks/explain
). Этот файл содержит код для относительного импорта (из @metakermit):
import sys
import os
module_path = os.path.abspath(os.path.join(os.pardir, os.pardir))
if module_path not in sys.path:
sys.path.append(module_path)
Таким образом, мне просто нужно сделать относительный импорт в файле project_path.py
, а не в записных книжках. Файлы с записных книжек затем нужно будет импортировать project_path
перед импортом lib
. Например, в 0.0-notebook.ipynb
:
import project_path
import lib
Предостережение заключается в том, что изменение импорта не сработает. ЭТО НЕ РАБОТАЕТ:
import lib
import project_path
Таким образом, необходимо соблюдать осторожность при импорте.
Ответ 4
Изучая эту тему самостоятельно и прочитав ответы, я рекомендую использовать библиотеку path.py, поскольку она предоставляет контекстный менеджер для изменения текущего рабочего каталога.
Затем у вас есть что-то вроде
import path
if path.Path('../lib').isdir():
with path.Path('..'):
import lib
Хотя вы можете просто опустить оператор isdir
.
Здесь я добавлю операторы печати, чтобы было легче следить за тем, что происходит
import path
import pandas
print(path.Path.getcwd())
print(path.Path('../lib').isdir())
if path.Path('../lib').isdir():
with path.Path('..'):
print(path.Path.getcwd())
import lib
print('Success!')
print(path.Path.getcwd())
который выводит в этом примере (где lib находится в /home/jovyan/shared/notebooks/by-team/data-vis/demos/lib
):
/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart
/home/jovyan/shared/notebooks/by-team/data-vis/demos
/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart
Поскольку в решении используется менеджер контекста, вы гарантированно вернетесь к своему предыдущему рабочему каталогу, независимо от того, в каком состоянии находилось ваше ядро перед ячейкой, и независимо от того, какие исключения вызываются при импорте кода вашей библиотеки.