Importieren Sie lokale Funktionen aus einem Modul in einem anderen Verzeichnis mit relativen Importen in Jupyter Notebook mit Python 3

126

Ich habe eine Verzeichnisstruktur ähnlich der folgenden

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

Bei der Arbeit in , notebook.jpynbwenn ich versuche , einen relativen Import zu verwenden , um eine Funktion für den Zugriff auf function()in module.pymit:

from ..project1.lib.module import function

Ich erhalte folgende Fehlermeldung:

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

Gibt es eine Möglichkeit, dies mithilfe relativer Importe zum Laufen zu bringen?

Beachten Sie, dass der Notebook-Server auf der Ebene des meta_projectVerzeichnisses instanziiert wird , sodass er Zugriff auf die Informationen in diesen Dateien haben sollte.

Beachten Sie auch, dass zumindest wie ursprünglich beabsichtigt project1nicht als Modul gedacht war und daher keine __init__.pyDatei enthält, sondern lediglich als Dateisystemverzeichnis gedacht war . Wenn die Lösung des Problems erfordert, dass es als Modul behandelt wird und eine __init__.pyDatei (auch eine leere) enthält, ist dies in Ordnung, reicht jedoch nicht aus, um das Problem zu lösen.

Ich teile dieses Verzeichnis zwischen Computern, und relative Importe ermöglichen es mir, überall denselben Code zu verwenden. Oft verwende ich Notebooks für das schnelle Prototyping. Daher sind Vorschläge, bei denen absolute Pfade gehackt werden, wahrscheinlich nicht hilfreich.


Bearbeiten: Dies ist anders als bei relativen Importen in Python 3 , bei denen es um relative Importe in Python 3 im Allgemeinen und insbesondere um das Ausführen eines Skripts aus einem Paketverzeichnis geht. Dies hat mit der Arbeit in einem Jupyter-Notizbuch zu tun, das versucht, eine Funktion in einem lokalen Modul in einem anderen Verzeichnis aufzurufen, das sowohl unterschiedliche allgemeine als auch bestimmte Aspekte aufweist.

mpacer
quelle
1
Gibt es __init__Dateien in Ihrem Paketverzeichnis?
Iron Fist
Ja, im libVerzeichnis.
mpacer
Bitte erwähnen Sie es in Ihrer Verzeichnisstruktur in Ihrer Frage
Iron Fist
Habe gerade diese Bearbeitung vorgenommen, sobald ich deinen ersten Kommentar gesehen habe :). Danke, dass du das verstanden hast.
mpacer
Mögliches Duplikat der relativen Importe in Python 3
baldr

Antworten:

172

Ich hatte fast das gleiche Beispiel wie Sie in diesem Notizbuch, in dem ich die Verwendung der Funktion eines benachbarten Moduls auf trockene Weise veranschaulichen wollte.

Meine Lösung bestand darin, Python über diesen zusätzlichen Modulimportpfad zu informieren, indem ich dem Notizbuch ein Snippet wie dieses hinzufügte:

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

Auf diese Weise können Sie die gewünschte Funktion aus der Modulhierarchie importieren:

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

Beachten Sie, dass leere __init__.pyDateien zu den Ordnern project1 / und lib / hinzugefügt werden müssen, wenn Sie diese noch nicht haben.

Metakermit
quelle
6
Dies löst das Problem, ein Paket über einen mehr oder weniger relativen Speicherort importieren zu können, jedoch nur indirekt. Ich weiß zufällig, dass Matthias Bussonier (@matt on SE) und Yuvi Panda (@yuvi on SE) github.com/ipython/ipynb entwickeln, die dies direkter angehen (z. B. indem sie relative Importe unter Verwendung der Standardsyntax nach dem Paket zulassen wird importiert). Ich werde Ihre Antwort vorerst akzeptieren, und wenn ihre Lösung für andere vollständig einsatzbereit ist, werde ich wahrscheinlich entweder eine Antwort auf die Verwendung schreiben oder einen von ihnen bitten, dies zu tun.
mpacer
Vielen Dank für den Hinweis auf die leere Init .py. Ich bin ein Python-Neuling und hatte Probleme, meine Klassen zum Importieren zu bringen. Ich habe Fehler in der Modulnotiz gefunden und eine leere Init .py hinzugefügt. Das Problem wurde behoben!
Pat Grady
5
Die leere init .py-Datei wird in Python 3 nicht mehr benötigt.
CathyQian
Zu Ihrer
Information
25

Kam hier auf der Suche nach Best Practices für die Abstraktion von Code in Submodule bei der Arbeit in Notebooks. Ich bin mir nicht sicher, ob es eine bewährte Methode gibt. Ich habe das vorgeschlagen.

Eine Projekthierarchie als solche:

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

Und von 20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

Dies funktioniert, da das Jupyter-Notizbuch den cdBefehl standardmäßig analysieren kann . Beachten Sie, dass hierfür keine Python Notebook-Magie verwendet wird. Es funktioniert einfach ohne Voranstellen %bash.

Mithilfe den man bedenkt , dass in 99 von 100 I in Docker arbeite Projekt Jupyter Docker Bilder , die folgende Modifikation ist idempotent

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection
Joshua Cook
quelle
Vielen Dank. Wirklich schrecklich die Beschränkungen dieser relativen Importe.
Michael
Ich verwende es auch, chdiranstatt es dem Pfad hinzuzufügen, da ich sowohl daran interessiert bin, aus dem Haupt-Repo zu importieren als auch mit einigen Dateien dort zu kommunizieren.
TheGrimmScientist
Leider das am meisten gehackte, was ich in Python mache. Ich kann jedoch keine bessere Lösung finden.
TheGrimmScientist
für einfache Idempotenz (damit dieselbe Zelle mehrmals ausgeführt werden kann und dasselbe Ergebnis erzielt wird) : if os.path.isdir('../lib/'): os.chdir('../lib'); oder, besser, verwenden Sie ../lib/db/mit Ihrem postgres.py, um nicht versehentlich in ein höheres Verzeichnis zu wechseln, das auch ein anderes enthält lib.
Michael
1
Ich mag diese Lösung, bis ich sie versehentlich cd ..zweimal ausgeführt habe .
minhle_r7
15

Bisher hat die akzeptierte Antwort für mich am besten funktioniert. Ich war jedoch immer besorgt, dass es ein wahrscheinliches Szenario gibt, in dem ich das notebooksVerzeichnis in Unterverzeichnisse umgestalten könnte, wobei ich das module_pathin jedem Notizbuch ändern muss. Ich habe beschlossen, in jedem Notebook-Verzeichnis eine Python-Datei hinzuzufügen, um die erforderlichen Module zu importieren.

Somit mit folgender Projektstruktur:

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

Ich habe die Datei project_path.pyin jedem Notizbuch-Unterverzeichnis ( notebooks/exploreund notebooks/explain) hinzugefügt . Diese Datei enthält den Code für relative Importe (von @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)

Auf diese Weise muss ich nur relative Importe innerhalb der project_path.pyDatei und nicht in die Notizbücher durchführen. Die Notebooks-Dateien müssten dann project_pathvor dem Import nur noch importiert werden lib. Zum Beispiel in 0.0-notebook.ipynb:

import project_path
import lib

Die Einschränkung hier ist, dass das Umkehren der Importe nicht funktionieren würde. DAS FUNKTIONIERT NICHT:

import lib
import project_path

Daher ist bei der Einfuhr Vorsicht geboten.

Gerges
quelle
3

Ich habe gerade diese hübsche Lösung gefunden:

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

Sie möchten nur einige Funktionen dieser Datei

from lib.store_load import your_function_name

Wenn die Python-Version> = 3.3 ist, benötigen Sie keine init.py-Datei im Ordner

Victor Callejas
quelle
3
Ich fand das sehr hilfreich. Ich werde hinzufügen, dass die folgende Änderung hinzugefügt werden sollte ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler
2

Ich recherchiere dieses Thema selbst und habe die Antworten gelesen. Ich empfehle die Verwendung der Bibliothek path.py, da sie einen Kontextmanager zum Ändern des aktuellen Arbeitsverzeichnisses bietet.

Sie haben dann so etwas wie

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

Sie können die isdirAnweisung jedoch einfach weglassen .

Hier füge ich Druckanweisungen hinzu, um zu verfolgen, was passiert

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

welche Ausgaben in diesem Beispiel (wo lib ist /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

Da die Lösung einen Kontextmanager verwendet, können Sie garantiert zu Ihrem vorherigen Arbeitsverzeichnis zurückkehren, unabhängig davon, in welchem ​​Status sich Ihr Kernel vor der Zelle befand und welche Ausnahmen beim Importieren Ihres Bibliothekscodes ausgelöst werden.

marr75
quelle
Dies funktioniert nicht in Kombination mit% autoreload, da der Modulpfad beim Neuladen nicht gefunden wird
Johannes,
1

Hier sind meine 2 Cent:

sys importieren

Ordnen Sie den Pfad zu, in dem sich die Moduldatei befindet. In meinem Fall war es der Desktop

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

Importieren Sie entweder das gesamte Mapping-Modul, ABER dann müssen Sie die .notation verwenden, um die Klassen wie Mapping zuzuordnen.Shipping ()

Import Mapping # Mapping.py ist der Name meiner Moduldatei

shipit = Mapping.Shipment () #Shipment ist der Name der Klasse, die ich im Mapping-Modul verwenden muss

Oder importieren Sie die spezifische Klasse aus dem Mapping-Modul

vom Mapping Import Mapping

shipit = Shipment () # Jetzt müssen Sie die .notation nicht mehr verwenden

Bobby
quelle
0

Ich habe festgestellt, dass Python-Dotenv hilft, dieses Problem ziemlich effektiv zu lösen. Ihre Projektstruktur ändert sich leicht, aber der Code in Ihrem Notizbuch ist für Notizbücher etwas einfacher und konsistenter.

Führen Sie für Ihr Projekt eine kleine Installation durch.

pipenv install python-dotenv

Das Projekt ändert sich dann zu:

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

Und schließlich ändert sich Ihr Import zu:

import os
import sys

from dotenv import find_dotenv


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

Ein +1 für dieses Paket ist, dass Ihre Notebooks mehrere Verzeichnisse tief sein können. python-dotenv findet das nächstgelegene in einem übergeordneten Verzeichnis und verwendet es. Ein +2 für diesen Ansatz ist, dass jupyter beim Start Umgebungsvariablen aus der ENV-Datei lädt. Double Whammy.

t.perk
quelle