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