web-dev-qa-db-fra.com

importer une fonction locale à partir d'un module hébergé dans un autre répertoire avec des importations relatives dans jupyter notebook à l'aide de python3

J'ai une structure de répertoire similaire à celle-ci

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

Lorsque je travaille dans notebook.jpynb si j'essaie d'utiliser une importation relative pour accéder à une fonction function() dans module.py avec:

from ..project1.lib.module import function

Je reçois l'erreur suivante

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

Y a-t-il un moyen de faire en sorte que cela fonctionne avec des importations relatives?

Notez que le serveur de notebook est instancié au niveau du répertoire meta_project, il doit donc avoir accès aux informations contenues dans ces fichiers.

Notez également que, au moins comme prévu à l'origine, project1 n'a pas été considéré comme un module et n'a donc pas de fichier __init__.py, il s'agissait simplement d'un répertoire du système de fichiers. Si la solution au problème nécessite de le traiter comme un module et d'inclure un fichier __init__.py (même un fichier vide), c'est correct, mais cela ne suffit pas pour résoudre le problème.

Je partage ce répertoire entre des machines et les importations relatives me permettent d’utiliser le même code partout, et j’utilise souvent des ordinateurs portables pour le prototypage rapide. Il est donc peu probable que les suggestions impliquant le piratage de chemins absolus soient utiles.


Edit: C’est différent de Importations relatives dans Python , qui parle d’importations relatives dans Python 3 en général et, en particulier, l'exécution d'un script à partir de l'intérieur. un répertoire de paquets. Il s’agit de travailler dans un cahier Jupyter en essayant d’appeler une fonction d’un module local situé dans un autre répertoire qui présente à la fois des aspects généraux et particuliers différents.

79
mpacer

J'avais presque le même exemple que vous dans ce cahier où je voulais illustrer l'utilisation de la fonction d'un module adjacent de manière DRY.

Ma solution consistait à informer Python de ce chemin d'importation de module supplémentaire en ajoutant un extrait de code similaire à celui-ci dans le bloc-notes:

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

Cela vous permet d'importer la fonction souhaitée depuis la hiérarchie de modules:

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

Notez qu'il est nécessaire d’ajouter des fichiers __init__.py vides à project1/ et à lib/ dossiers si vous ne les avez pas déjà.

117
metakermit

Nous sommes venus ici à la recherche des meilleures pratiques en matière de synthèse de code pour les sous-modules lorsque vous travaillez dans Notebooks Je ne suis pas sûr qu'il existe une meilleure pratique. Je propose cela.

Une hiérarchie de projet en tant que telle:

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

Et de 20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

Cela fonctionne car, par défaut, le Jupyter Notebook peut analyser la commande cd. Notez que cela n’utilise pas Python Magie du portable. Cela fonctionne simplement sans ajouter de préfixe %bash.

Considérant que 99 fois sur 100, je travaille dans Docker avec l’un des images du projet Jupyter Docker , la modification suivante est idempotent

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection
16
Joshua Cook

Jusqu'ici, la réponse acceptée a fonctionné le mieux pour moi. Cependant, ma préoccupation a toujours été qu'il existe un scénario probable dans lequel je pourrais refactoriser le répertoire notebooks en sous-répertoires, ce qui nécessiterait de modifier le module_path dans chaque bloc-notes. J'ai décidé d'ajouter un fichier python dans chaque répertoire de bloc-notes pour importer les modules requis.

Ainsi, ayant la structure de projet suivante:

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

J'ai ajouté le fichier project_path.py dans chaque sous-répertoire du bloc-notes (notebooks/explore et notebooks/explain). Ce fichier contient le code des importations relatives (de @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)

De cette façon, j'ai juste besoin de faire des importations relatives dans le fichier project_path.py, et non dans les cahiers. Les fichiers de cahiers n'auraient alors plus qu'à importer project_path avant d'importer lib. Par exemple dans 0.0-notebook.ipynb:

import project_path
import lib

La mise en garde est que l'inversion des importations ne fonctionnerait pas. CELA NE FONCTIONNE PAS:

import lib
import project_path

Il faut donc faire attention lors des importations.

8
Gerges