Импортировать локальную функцию из модуля, расположенного в другом каталоге, с относительным импортом в блокнот 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 в целом и - в частности - о запуске скрипта из каталога пакета. Это связано с работой внутри ноутбука Jupyter, пытающегося вызвать функцию в локальном модуле в другом каталоге, который имеет как общие, так и частные аспекты.
14 ответов
У меня был почти такой же пример, как у вас в этой записной книжке, где я хотел проиллюстрировать использование функции соседнего модуля в СУХОЙ манере.
Мое решение состояло в том, чтобы сообщить 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/, если у вас их еще нет.
Пришел сюда в поисках лучших практик по абстрагированию кода для субмодулей при работе в Блокнотах. Я не уверен, что есть лучшая практика. Я предлагал это.
Иерархия проекта как таковая:
├── 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
All other answers here depends on adding code the the notebook(!)
In my opinion is bad practice to hardcode a specific path into the notebook code, or otherwise depend on the location, since this makes it really hard to refactor you code later on. Instead I would recommend you to add the root project folder to PYTHONPATH when starting up your Jupyter notebook server, either directly from the project folder like so
env PYTHONPATH=`pwd` jupyter notebook
or if you are starting it up from somewhere else, use the absolute path like so
env PYTHONPATH=/Users/foo/bar/project/ jupyter notebook
До сих пор принятый ответ работал лучше всего для меня. Однако меня всегда беспокоило то, что существует вероятный сценарий, в котором я мог бы 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
Таким образом, необходимо соблюдать осторожность при импорте.
Я только что нашел это красивое решение:
import sys; sys.path.insert(0, '..') # add parent folder path where lib folder is
import lib.store_load # store_load is a file on my library folder
Вам просто нужны некоторые функции этого файла
from lib.store_load import your_function_name
Если версия python >= 3.3 вам не нужен файл init.py в папке
Если вы работаете через расширение Jupyter в VS Code, я обнаружил, что теперь вы можете указать этот путь в своемsettings.json
файл.
{
"jupyter.notebookFileRoot": "/path/to/your/module/root"
}
Изменить: Или, чтобы установить его в корне вашей рабочей области в более общем виде:
{
"jupyter.notebookFileRoot": "${workspaceFolder}"
}
Я обнаружил, что python-dotenv помогает довольно эффективно решить эту проблему. Структура вашего проекта немного изменится, но код в вашей записной книжке станет немного проще и единообразнее для разных записных книжек.
Для вашего проекта выполните небольшую установку.
pipenv install python-dotenv
Затем проект изменится на:
├── .env (this can be empty)
├── ipynb
│ ├── 20170609-Examine_Database_Requirements.ipynb
│ └── 20170609-Initial_Database_Connection.ipynb
└── lib
├── __init__.py
└── postgres.py
И, наконец, ваш импорт изменится на:
import os
import sys
from dotenv import find_dotenv
sys.path.append(os.path.dirname(find_dotenv()))
+1 для этого пакета - ваши записные книжки могут содержать несколько каталогов. python-dotenv найдет ближайший в родительском каталоге и будет использовать его. +2 для этого подхода заключается в том, что jupyter будет загружать переменные среды из файла.env при запуске. Двойной удар.
Изучив эту тему самостоятельно и прочитав ответы, я рекомендую использовать библиотеку 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
Поскольку решение использует диспетчер контекста, вы гарантированно вернетесь в свой предыдущий рабочий каталог, независимо от того, в каком состоянии находилось ядро перед ячейкой, и независимо от того, какие исключения возникли при импорте кода вашей библиотеки.
Вот мои 2 цента:
import sys
сопоставьте путь, по которому расположен файл модуля. В моем случае это был рабочий стол
sys.path.append('/ Пользователи / Джон / Рабочий стол')
Либо импортируйте весь модуль сопоставления, НО тогда вы должны использовать.notation для сопоставления классов, таких как mapping.Shipping ()
import mapping # mapping.py - это имя моего файла модуля
shipit = mapping.Shipment() #Shipment - это имя класса, который мне нужно использовать в модуле сопоставления.
Или импортируйте конкретный класс из модуля сопоставления
из сопоставления импорт карт
shipit = Shipment() # Теперь не нужно использовать.notation
Я разозлился, поэтому написал пакет .
pip install package_root
и тогда ты сможешьimport package_root # into your PYTHONPATH
.
Это работает, просматривая стек вызовов, чтобы определить, какой файл импортированpackage_root
а затем пройти вверх по каталогам, чтобы найти первый каталог, в котором отсутствует файл.
Итак, на практике вам нужно будет добавить__init__.py
файлы в каталоги вашей записной книжки, как если бы вы заставляли пакеты Python работать.
For those who don't understand the solution as I didn't, you have to go as deep to the directory as you need for your specific problem. Solution for this errorr:
No module named 'your_folder'
My notebook that I work with is in:
C:\Users\vojte\projects\predicta\EDA\apartments\EDA.ipynb
I want to import this notebook:
C:\Users\vojte\projects\functions\functions.ipynb
I had to modify the solution above by @metakermit to this solution:
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 functions import functions as f
You can check the
sys.path
for appended directory.In this example this line was appended:
C:\\Users\\vojte\\projects
Вот общее решение, основанное на этом ответе , которое не требует ни указания имени родительской папки, ни изменения текущего рабочего каталога.
Просто обновитеroot_parent_level
с количеством родительских уровней, где начинается относительный импорт, и убедитесь, что__init__.py
существует в каждом подпакете.
if "PKG" not in globals(): # `PKG` is used just to avoid re-excuting the cell more than once
root_parent_level = 2
import importlib, sys, pathlib
PKG = %pwd
PKG = pathlib.Path(PKG)
root = PKG
full_pkg = f"{root.name}"
for _ in range(root_parent_level):
root = root.parent
full_pkg = f"{root.name}.{full_pkg}"
MODULE_PATH = f"{root}{pathlib.os.path.sep}__init__.py"
MODULE_NAME = f"{root.name}"
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
__package__ = full_pkg
Если вы используете VSCODE, вы можете включить его в настройках: Настройки VSCODE
Улучшение @ Навина кулинарной ответ при помощи
cd..
, чтобы убедиться, что вы не перезапустили ячейку и не испортили свой каталог при использовании
run all
без
restart
, используйте вместо этого этот код:
if 'NOTEBOOK_INITIATED_FLAG' not in globals():
NOTEBOOK_INITIATED_FLAG = True
%cd ..
%pwd
NOTEBOOK_INITIATED_FLAG
используется в качестве заполнителя, который отмечает, что ядро уже запущено, поэтому изменение каталога не требуется.