Importa la funzione locale da un modulo ospitato in un'altra directory con relative importazioni in Jupyter Notebook utilizzando Python 3


127

Ho una struttura di directory simile alla seguente

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

Quando lavoro notebook.jpynbse provo a utilizzare un'importazione relativa per accedere a una funzione function()in module.pycon:

from ..project1.lib.module import function

Ottengo il seguente errore:

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

C'è un modo per farlo funzionare utilizzando le importazioni relative?

Si noti che il server notebook viene istanziato a livello di meta_projectdirectory, quindi dovrebbe avere accesso alle informazioni in quei file.

Si noti, inoltre, che almeno come originariamente previsto project1non è stato pensato come un modulo e quindi non ha un __init__.pyfile, è stato semplicemente inteso come una directory del file system. Se la soluzione al problema richiede di trattarlo come un modulo e includere un __init__.pyfile (anche uno vuoto) va bene, ma farlo non è sufficiente per risolvere il problema.

Condivido questa directory tra le macchine e le importazioni relative mi consentono di utilizzare lo stesso codice ovunque, e spesso uso i notebook per la prototipazione rapida, quindi è improbabile che i suggerimenti che coinvolgono l'hacking di percorsi assoluti siano utili.


Modifica: questo è diverso dalle importazioni relative in Python 3 , che parla delle importazioni relative in Python 3 in generale e, in particolare, dell'esecuzione di uno script da una directory del pacchetto. Questo ha a che fare con il lavoro all'interno di un notebook jupyter cercando di chiamare una funzione in un modulo locale in un'altra directory che ha aspetti sia generali che particolari.


1
ci sono __init__file nella directory del pacchetto?
Iron Fist

Sì, nella libdirectory.
mpacer

Per favore, menzionalo nella struttura della tua directory nella tua domanda
Iron Fist

Ho appena apportato la modifica non appena ho visto il tuo primo commento :). Grazie per averlo capito.
mpacer

Possibile duplicato delle importazioni relative in Python 3
baldr

Risposte:


174

Ho avuto quasi lo stesso esempio di te in questo quaderno in cui volevo illustrare l'uso della funzione di un modulo adiacente in modo DRY.

La mia soluzione era dire a Python di quel percorso di importazione del modulo aggiuntivo aggiungendo uno snippet come questo al notebook:

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

Ciò consente di importare la funzione desiderata dalla gerarchia del modulo:

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

Nota che è necessario aggiungere __init__.pyfile vuoti alle cartelle project1 / e lib / se non li hai già.


6
Questo risolve il problema di poter importare un pacchetto utilizzando quella che è più o meno una posizione relativa, ma solo indirettamente. Mi è capitato di sapere che Matthias Bussonier (@matt su SE) e Yuvi Panda (@yuvi su SE) stanno sviluppando github.com/ipython/ipynb che affronterà questo problema più direttamente (ad esempio, consentendo le importazioni relative utilizzando la sintassi standard una volta che il loro pacchetto è importato). Per ora accetterò la tua risposta e quando la loro soluzione sarà completamente pronta per essere utilizzata da altri, probabilmente scriverò una risposta su come usarla o chiederò a uno di loro di farlo.
mpacer

grazie per aver evidenziato il vuoto init .py Sono un principiante di Python e ho avuto problemi a importare le mie classi. Stavo ricevendo la nota del modulo trovato errori, l'aggiunta di init vuoto .py ha risolto il problema!
Pat Grady

5
Il file init .py vuoto non è più necessario in Python 3.
CathyQian

FYI: c'è un visualizzatore per notebook: nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/…
thoroc

26

Sono venuto qui alla ricerca di best practice per astrarre il codice nei sottomoduli quando si lavora in Notebook. Non sono sicuro che esista una buona pratica. Ho proposto questo.

Una gerarchia di progetto in quanto tale:

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

E da 20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

Funziona perché per impostazione predefinita il Jupyter Notebook può analizzare il cdcomando. Nota che questo non fa uso della magia di Python Notebook. Funziona semplicemente senza anteporre %bash.

Considerando che 99 volte su 100 sto lavorando in Docker utilizzando una delle immagini Docker di Project Jupyter , la seguente modifica è idempotente

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

Grazie. Veramente orribili le restrizioni di queste importazioni relative.
Michael

Anch'io uso chdirpiuttosto che aggiungere al percorso, dal momento che sono interessato sia all'importazione dal repository principale che all'interfacciamento con alcuni file lì.
TheGrimmScientist

Purtroppo, la cosa più hackerata che faccio in Python. Tuttavia, non riesco a trovare una soluzione migliore.
TheGrimmScientist

per semplice idempotenza (consentendo alla stessa cella di essere eseguita più volte e ottenere lo stesso risultato) if os.path.isdir('../lib/'): os.chdir('../lib'):; o, meglio, da usare ../lib/db/con il tuo in postgres.pymodo da non chdir accidentalmente fino a una directory superiore che ne contiene anche un'altra lib.
michael

1
Mi piace questa soluzione finché non l'ho eseguita accidentalmente cd ..due volte.
minhle_r7

15

Finora, la risposta accettata ha funzionato meglio per me. Tuttavia, la mia preoccupazione è sempre stata che esiste uno scenario probabile in cui potrei rifattorizzare la notebooksdirectory in sottodirectory, richiedendo la modifica di module_pathin ogni notebook. Ho deciso di aggiungere un file python all'interno di ciascuna directory del notebook per importare i moduli richiesti.

Quindi, avendo la seguente struttura di progetto:

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

Ho aggiunto il file project_path.pyin ogni sottodirectory del notebook ( notebooks/exploree notebooks/explain). Questo file contiene il codice per le importazioni relative (da @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)

In questo modo, devo solo fare importazioni relative all'interno del project_path.pyfile e non nei notebook. I file dei taccuini dovrebbero quindi solo essere importati project_pathprima dell'importazione lib. Ad esempio in 0.0-notebook.ipynb:

import project_path
import lib

L'avvertenza qui è che invertire le importazioni non funzionerebbe. QUESTO NON FUNZIONA:

import lib
import project_path

Quindi è necessario prestare attenzione durante le importazioni.


3

Ho appena trovato questa bella soluzione:

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

Vuoi solo alcune funzioni di quel file

from lib.store_load import your_function_name

Se la versione di python> = 3.3 non è necessario il file init.py nella cartella


3
L'ho trovato molto utile. Aggiungo che dovrebbe essere aggiunta la seguente modifica ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler

2

Facendo ricerche su questo argomento personalmente e dopo aver letto le risposte, consiglio di utilizzare il Facendo libreria path.py poiché fornisce un gestore di contesto per modificare la directory di lavoro corrente.

Quindi hai qualcosa di simile

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

Tuttavia, potresti semplicemente omettere il file isdir dichiarazione.

Qui aggiungerò istruzioni di stampa per semplificare il monitoraggio di ciò che sta accadendo

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

che restituisce in questo esempio (dove lib è in /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

Poiché la soluzione utilizza un gestore di contesto, è garantito che tornerai alla directory di lavoro precedente, indipendentemente dallo stato in cui si trovava il kernel prima della cella e indipendentemente dalle eccezioni generate importando il codice della libreria.


Questo non funzionerà in combinazione con% autoreload, poiché il percorso del modulo non verrà trovato al momento della ricarica
Johannes

1

Ecco i miei 2 centesimi:

import sys

mappare il percorso in cui si trova il file del modulo. Nel mio caso era il desktop

sys.path.append ( '/ Users / John / Desktop')

O importa l'intero modulo di mappatura MA poi devi usare .notation per mappare le classi come mapping.Shipping ()

import mapping # mapping.py è il nome del file del mio modulo

shipit = mapping.Shipment () #Shipment è il nome della classe che devo utilizzare nel modulo di mappatura

Oppure importa la classe specifica dal modulo di mappatura

dalla mappatura import Mapping

shipit = Shipment () #Ora non è necessario utilizzare la .notation


0

L'ho trovato python-dotenv aiuta a risolvere questo problema in modo abbastanza efficace. La struttura del tuo progetto finisce per cambiare leggermente, ma il codice nel tuo notebook è un po 'più semplice e coerente tra i notebook.

Per il tuo progetto, fai una piccola installazione.

pipenv install python-dotenv

Quindi, il progetto cambia in:

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

Infine, l'importazione cambia in:

import os
import sys

from dotenv import find_dotenv


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

Un +1 per questo pacchetto è che i tuoi taccuini possono contenere diverse directory. python-dotenv troverà quello più vicino in una directory genitore e lo userà. Un +2 per questo approccio è che jupyter caricherà le variabili d'ambiente dal file .env all'avvio. Doppio colpo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.