Імпортуйте локальну функцію з модуля, розміщеного в іншому каталозі, з відносним імпортом у Jupyter Notebook за допомогою Python 3


127

У мене структура каталогів схожа на наступну

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 загалом і, зокрема, - запуск сценарію з каталогу пакетів. Це пов'язано з роботою в зошиті юпітера, що намагається викликати функцію в локальному модулі в іншому каталозі, який має як різні загальні, так і особливі аспекти.


1
чи є __init__в каталозі пакунків файли?
Залізний кулак

Так, у libдовіднику.
mpacer

Будь ласка, згадайте це у своїй структурі каталогів у своєму запитанні
Iron Fist

Щойно зробив це редагування, як тільки побачив ваш перший коментар :) Дякую, що ви це зробили.
mpacer

Можливий дублікат відносного імпорту в Python 3
baldr

Відповіді:


174

Я мав майже такий самий приклад, як і ви в цьому зошиті, де я хотів проілюструвати використання суміжного модуля функцією DRY.

Моє рішення полягало в тому, щоб повідомити 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 /, якщо у вас їх вже немає.


6
Це вирішує проблему можливості імпортувати пакет, використовуючи більш-менш відносне розташування, але лише опосередковано. Я випадково знаю, що Маттіас Буссоньє (@matt на SE) та Юві Панда (@yuvi на SE) розробляють github.com/ipython/ipynb, які вирішуватимуть це безпосередньо (наприклад, дозволяючи відносному імпорту, використовуючи стандартний синтаксис один раз у пакеті імпортується). Я зараз прийму вашу відповідь, і коли їх рішення буде повністю готове до використання іншими, я, мабуть, або напишу відповідь про те, як ним користуватися, або попрошу когось із них зробити це.
mpacer

дякую за вказівку на порожній init .py Я новачок у пітоні і у мене виникли проблеми з імпортом моїх класів. Я отримував помилки в модулі, знайшов помилки, додавши порожній init .py вирішив проблему!
Пат Грейді

5
Порожній файл init .py більше не потрібен в Python 3.
CathyQian

FYI: є переглядач для ноутбука: nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/…
торок

26

Сюди прийшли пошуки кращих практик абстрагування коду на підмодулі під час роботи в «Блокнотах». Я не впевнений, що є найкраща практика. Я це пропоную.

Ієрархія проекту як така:

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

Це працює, тому що за замовчуванням ноутбук Юпітера може проаналізувати cdкоманду. Зауважте, що це не використовує магію ноутбука Python. Це просто працює без попереднього %bash.

Зважаючи на те, що 99 із 100, які я працюю в Докер, використовуючи одне із зображень Project Jupyter Docker , наступна модифікація є ідентичною

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

Дякую. Дійсно жахливі обмеження цього відносного імпорту.
Майкл

Я теж використовую, chdirа не додаю до шляху, оскільки мені обом цікаво імпортувати з головного репо, а також взаємодіяти з деякими файлами там.
TheGrimmScientist

На жаль, сама зламана річ, яку я роблю в пітоні. Але я не можу знайти кращого рішення.
TheGrimmScientist

для простої ідентифікації (дозволяє одній клітці запускатись кілька разів і отримувати той самий результат) if os.path.isdir('../lib/'): os.chdir('../lib'):; або, що краще, використовувати ../lib/db/з вашим, postgres.pyщоб не випадково chdir до вищого каталогу, що також містить інший lib.
michael

1
Мені подобається це рішення, поки я випадково не був виконаний cd ..двічі.
minhle_r7

15

Поки що прийнята відповідь найкраще працювала для мене. Однак, я завжди хвилююсь, що існує ймовірний сценарій, коли я можу переробляти 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

Тому під час імпорту потрібно бути обережним.


3

Я щойно знайшов це гарне рішення:

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 у папці


3
Я вважаю це дуже корисним. Додам, що слід додати наступну модифікацію ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Яков Бресслер

2

Я сам досліджував цю тему і прочитавши відповіді, рекомендую використовувати бібліотеку 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

Оскільки рішення використовує диспетчер контексту, ви гарантовано повернетеся до попереднього робочого каталогу, незалежно від того, у якому стані знаходилось ваше ядро ​​до осередку, і незалежно від того, які винятки викидаєте при імпорті коду бібліотеки.


Це не працюватиме в поєднанні з% autoloadload, оскільки шлях модуля не знайдеться під час перезавантаження
Йоханнес

1

Ось мої 2 копійки:

імпортувати sys

відображення шляху, де знаходиться файл модуля. У моєму випадку це був робочий стіл

sys.path.append ('/ Користувачі / Джон / Робочий стіл ")

Або імпортуйте весь модуль картографування, АЛЕ вам потрібно використовувати .notation для відображення таких класів, як mapping.Shipping ()

імпортувати зіставлення # mapping.py - це ім'я мого файлу модуля

shipit = mapping.Shipment () #Shipment - це назва класу, який мені потрібно використовувати в модулі відображення

Або імпортувати певний клас із модуля відображення

з картографування імпортного картографування

shipit = Відправка () #Зараз вам не потрібно використовувати .notation


0

Я виявив, що 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 для цього підходу полягає в тому, що юпітер завантажує змінні середовища з .env-файлу при запуску. Подвійна валмі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.