Importuj funkcję lokalną z modułu znajdującego się w innym katalogu z relatywnymi importami w Jupyter Notebook przy użyciu Pythona 3

127

Mam strukturę katalogów podobną do poniższej

meta_project
    project1
        __init__.py
        lib
            module.py
            __init__.py
    notebook_folder
        notebook.jpynb

Podczas pracy w programie, notebook.jpynbjeśli spróbuję użyć importu względnego, aby uzyskać dostęp do funkcji function()w module.py:

from ..project1.lib.module import function

Otrzymuję następujący błąd:

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

Czy jest jakiś sposób, aby to działało przy użyciu importu względnego?

Należy zauważyć, że serwer notebooków jest tworzony na poziomie meta_projectkatalogu, więc powinien mieć dostęp do informacji w tych plikach.

Zauważ również, że przynajmniej tak jak pierwotnie zamierzano, project1nie był uważany za moduł i dlatego nie ma __init__.pypliku, miał po prostu służyć jako katalog systemu plików. Jeśli rozwiązanie problemu wymaga potraktowania go jako modułu i dołączenia __init__.pypliku (nawet pustego), to jest w porządku, ale nie wystarczy, aby rozwiązać problem.

Dzielę ten katalog między maszyny, a względne importy pozwalają mi używać tego samego kodu wszędzie i często używam notebooków do szybkiego prototypowania, więc sugestie, które obejmują hakowanie razem bezwzględnych ścieżek, raczej nie będą pomocne.


Edycja: W przeciwieństwie do importu względnego w Pythonie 3 , który ogólnie mówi o imporcie względnym w Pythonie 3, a zwłaszcza o uruchamianiu skryptu z katalogu pakietu. Ma to związek z pracą w notebooku jupyter, próbując wywołać funkcję w module lokalnym w innym katalogu, która ma różne aspekty ogólne i szczegółowe.

mpacer
źródło
1
czy __init__w katalogu z pakietami są jakieś pliki?
Iron Fist
Tak, w libkatalogu.
mpacer
Proszę, wspomnij o tym w swojej strukturze katalogów w swoim pytaniu
Iron Fist,
Właśnie dokonałem tej edycji, gdy tylko zobaczyłem Twój pierwszy komentarz :). Dziękuję, że to złapałeś.
mpacer
Możliwy duplikat importu względnego w Pythonie 3
baldr,

Odpowiedzi:

174

Miałem prawie taki sam przykład jak ty w tym notatniku, w którym chciałem zilustrować użycie funkcji sąsiedniego modułu w sposób SUCHY.

Moim rozwiązaniem było poinformowanie Pythona o tej dodatkowej ścieżce importu modułu przez dodanie fragmentu takiego jak ten do notatnika:

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)

Pozwala to na zaimportowanie żądanej funkcji z hierarchii modułów:

from project1.lib.module import function
# use the function normally
function(...)

Pamiętaj, że konieczne jest dodanie pustych __init__.pyplików do folderów project1 / i lib /, jeśli jeszcze ich nie masz.

metakermit
źródło
6
To rozwiązuje problem możliwości zaimportowania pakietu przy użyciu mniej więcej względnej lokalizacji, ale tylko pośrednio. Tak się składa, że ​​wiem, że Matthias Bussonier (@matt na SE) i Yuvi Panda (@yuvi na SE) opracowują github.com/ipython/ipynb, który zajmie się tym bardziej bezpośrednio (np. Zezwalając na import względny przy użyciu standardowej składni, gdy ich pakiet jest importowany). Przyjmuję na razie twoją odpowiedź, a gdy ich rozwiązanie będzie już gotowe do użycia przez innych, prawdopodobnie napiszę odpowiedź, jak z niego korzystać, albo poproszę jednego z nich o zrobienie tego.
mpacer
dzięki za wskazanie pustego pliku init .py Jestem nowicjuszem w Pythonie i miałem problemy z zaimportowaniem moich klas. Otrzymywałem informację o błędzie znalezienia modułu, dodanie pustego pliku init .py naprawiło problem!
Pat Grady
5
Pusty plik init .py nie jest już potrzebny w Pythonie 3.
CathyQian,
FYI: istnieje przeglądarka dla notebooka: nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/…
thoroc
26

Przyszedłem tutaj, szukając najlepszych praktyk w zakresie wyodrębniania kodu do modułów podrzędnych podczas pracy w notatnikach. Nie jestem pewien, czy istnieje najlepsza praktyka. Ja to proponowałem.

Hierarchia projektu jako taka:

├── ipynb
   ├── 20170609-Examine_Database_Requirements.ipynb
   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

I od 20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

Działa to, ponieważ domyślnie notatnik Jupyter może przeanalizować cdpolecenie. Zauważ, że nie wykorzystuje to magii Python Notebook. Po prostu działa bez poprzedzania %bash.

Biorąc pod uwagę, że 99 razy na 100 pracuję w Dockerze przy użyciu jednego z obrazów Project Jupyter Docker , następująca modyfikacja jest idempotentna

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection
Joshua Cook
źródło
Dzięki. Naprawdę okropne ograniczenia tego względnego importu.
Michael
Ja też używam chdirzamiast dodawać do ścieżki, ponieważ jestem zainteresowany importowaniem z głównego repozytorium, a także łączeniem się z niektórymi plikami tam.
TheGrimmScientist
Niestety, najbardziej zhakowana rzecz, jaką robię w Pythonie. Jednak nie mogę znaleźć lepszego rozwiązania.
TheGrimmScientist
dla prostej idempotencji (pozwalającej tej samej komórce działać wiele razy i uzyskać ten sam wynik) if os.path.isdir('../lib/'): os.chdir('../lib'):; lub lepiej, używaj ../lib/db/z twoim postgres.py, aby przypadkowo nie zmieniać katalogu do wyższego katalogu zawierającego również inny lib.
michael
1
Podoba mi się to rozwiązanie, dopóki przypadkowo nie wykonałem cd ..dwukrotnie.
minhle_r7
15

Jak dotąd przyjęta odpowiedź działa najlepiej dla mnie. Jednak zawsze martwiłem się, że istnieje prawdopodobny scenariusz, w którym mógłbym notebookszmienić katalog na podkatalogi, wymagając zmiany module_pathw każdym notatniku. Zdecydowałem się dodać plik Pythona w każdym katalogu notebooka, aby zaimportować wymagane moduły.

Zatem mając następującą strukturę projektu:

project
|__notebooks
   |__explore
      |__ notebook1.ipynb
      |__ notebook2.ipynb
      |__ project_path.py
   |__ explain
       |__notebook1.ipynb
       |__project_path.py
|__lib
   |__ __init__.py
   |__ module.py

Dodałem plik project_path.pyw każdym podkatalogu notatnika ( notebooks/explorei notebooks/explain). Ten plik zawiera kod do importu względnego (z @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)

W ten sposób wystarczy dokonać względnego importu w project_path.pypliku, a nie w notatnikach. Pliki notatników musiałyby wtedy po prostu zaimportować project_pathprzed zaimportowaniem lib. Na przykład w 0.0-notebook.ipynb:

import project_path
import lib

Zastrzeżenie polega na tym, że cofanie importu nie zadziała. TO NIE DZIAŁA:

import lib
import project_path

Dlatego należy zachować ostrożność podczas importu.

Gerges
źródło
3

Właśnie znalazłem to ładne rozwiązanie:

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

Potrzebujesz tylko niektórych funkcji tego pliku

from lib.store_load import your_function_name

Jeśli wersja Pythona> = 3.3, nie potrzebujesz pliku init.py w folderze

Victor Callejas
źródło
3
Uznałem to za bardzo pomocne. Dodam, że należy dodać następującą modyfikację ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler
2

Samodzielne badanie tego tematu i przeczytanie odpowiedzi polecam korzystanie z biblioteki path.py, ponieważ zapewnia ona menedżera kontekstu do zmiany bieżącego katalogu roboczego.

Masz wtedy coś takiego

import path
if path.Path('../lib').isdir():
    with path.Path('..'):
        import lib

Chociaż możesz po prostu pominąć isdirstwierdzenie.

Tutaj dodam instrukcje drukowania, aby ułatwić śledzenie tego, co się dzieje

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())

które wyprowadza w tym przykładzie (gdzie lib jest w /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

Ponieważ rozwiązanie korzysta z menedżera kontekstu, masz gwarancję powrotu do poprzedniego katalogu roboczego, bez względu na stan jądra przed komórką i bez względu na to, jakie wyjątki są zgłaszane, importując kod biblioteki.

marr75
źródło
To nie zadziała w połączeniu z% autoreload, ponieważ ścieżka modułu nie zostanie znaleziona w czasie przeładowania
Johannes
1

Oto moje 2 centy:

import sys

odwzoruj ścieżkę, w której znajduje się plik modułu. W moim przypadku był to pulpit

sys.path.append („/ Users / John / Desktop”)

Albo zaimportuj cały moduł mapowania, ALE wtedy musisz użyć .notation, aby zmapować klasy, takie jak mapping.

import mapping # mapping.py to nazwa mojego pliku modułu

shipit = mapping.Shipment () #Shipment to nazwa klasy, której muszę użyć w module mapowania

Lub zaimportuj określoną klasę z modułu mapowania

z mapowania import Mapping

shipit = Shipment () #Teraz nie musisz używać .notacji

Konstabl
źródło
0

Odkryłem, że python-dotenv dość skutecznie pomaga rozwiązać ten problem. W końcu struktura Twojego projektu nieco się zmienia, ale kod w Twoim notatniku jest nieco prostszy i spójny we wszystkich notatnikach.

W swoim projekcie zrób małą instalację.

pipenv install python-dotenv

Następnie projekt zmienia się na:

├── .env (this can be empty)
├── ipynb
   ├── 20170609-Examine_Database_Requirements.ipynb
   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

Na koniec import zmienia się na:

import os
import sys

from dotenv import find_dotenv


sys.path.append(os.path.dirname(find_dotenv()))

+1 dla tego pakietu oznacza, że ​​Twoje notatniki mogą być głębokie na kilka katalogów. python-dotenv znajdzie najbliższy w katalogu nadrzędnym i użyje go. +2 dla tego podejścia oznacza, że ​​jupyter ładuje zmienne środowiskowe z pliku .env podczas uruchamiania. Double whammy.

t.perk
źródło