Python 3を使用して、Jupyter Notebookで相対インポートを使用して、別のディレクトリに格納されているモジュールからローカル関数をインポートする


126

次のようなディレクトリ構造があります

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の相対インポートとは異なります。Python3は、一般的にはPython 3の相対インポートについて、特にパッケージディレクトリ内からスクリプトを実行することについて説明しています。これは、異なる一般的な側面と特定の側面の両方を持つ別のディレクトリのローカルモジュールの関数を呼び出そうとするjupyterノートブック内での作業に関係しています。


1
__init__パッケージディレクトリにファイルはありますか?
Iron Fist

はい、libディレクトリにあります。
mpacer

ご質問の際は、ディレクトリ構造で言及してください
Iron Fist

私はあなたの最初のコメントを見たらすぐにその編集を行いました:)。捕まえてくれてありがとう。
mpacer

回答:


173

このノートブックであなたとほとんど同じ例がありましたが、隣接するモジュールの機能の使用方法を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
これにより、多かれ少なかれ相対的な場所を使用してパッケージをインポートできるという問題が解決されますが、間接的にしかできません。Matthias Bussonier(@matt on SE)とYuvi Panda(@yuvi on SE)がgithub.com/ipython/ipynbを開発していることを知りました。インポートされます)。私は今のところあなたの答えを受け入れます、そして彼らの解決策が他の人が使うために完全に準備ができているとき、私はおそらくそれを使う方法について答えを書くか、そうするように彼らの一人に頼むでしょう。
mpacer

空のinit .pyを指摘してくれてありがとう。私はPythonの初心者で、クラスをインポートするのに問題がありました。モジュールノートでエラーが見つかりました。空のinit .pyを追加すると問題が修正されました!
Pat Grady

5
空のinit .pyファイルは、Python 3では不要になりました
。– CathyQian

FYI:ノートブック用のビューアがあります:nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/...
thoroc

25

ノートブックで作業するときにサブモジュールにコードを抽象化するためのベストプラクティスを探してここに来ました。ベストプラクティスがあるかどうかはわかりません。私はこれを提案しています。

そのようなプロジェクト階層:

├── 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

デフォルトでは、Jupyter Notebookがcdコマンドを解析できるため、これは機能します。これはPython Notebookマジックを利用しないことに注意してください。それは単に追加することなく機能し%bashます。

Project Jupyter Dockerイメージの 1つを使用してDockerで作業している100のうち99回を考えると、次の変更べき等です

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

ありがとう。この相対的な輸入の制限は本当に恐ろしい。
マイケル

chdirパスに追加するのではなく、私も使用します。メインリポジトリからのインポートと、そこにあるいくつかのファイルとのインターフェースの両方に興味があるからです。
TheGrimmScientist 2017年

悲しいことに、私がpythonで最もハッキングしたこと。それでも、より良い解決策を見つけることができません。
TheGrimmScientist 2017年

単純なべき等の場合(同じセルを複数回実行して同じ結果を得ることができます)if os.path.isdir('../lib/'): os.chdir('../lib'):; または、別のを含む上位のディレクトリに誤ってchdirしないように、../lib/db/と一緒に使用します。postgres.pylib
マイケル

1
私が誤ってcd ..2回実行するまで、このソリューションが好きです。
minhle_r7 2018年

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/explorenotebooks/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,"..")
Yaakov Bressler

2

このトピックを自分で調べて回答を読んだら、path.pyライブラリを使用することをお勧めします。これは、現在の作業ディレクトリを変更するためのコンテキストマネージャを提供するためです。

次に、あなたは次のようなものを持っています

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

ただし、isdirステートメントを省略してもかまいません。

ここで、何が起こっているのか簡単に追跡できるように、printステートメントを追加します

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

ソリューションはコンテキストマネージャーを使用するので、セルの前のカーネルの状態やライブラリコードのインポートによってスローされた例外に関係なく、以前の作業ディレクトリに戻ることが保証されます。


モジュールパスがリロード時に見つからないため、これは%autoreloadと組み合わせて機能しません
ヨハネス

1

これが私の2セントです。

インポートシステム

モジュールファイルが配置されているパスをマップします。私の場合、それはデスクトップでした

sys.path.append( '/ユーザー/ジョン/デスクトップ')

マッピングモジュール全体をインポートするか、.notationを使用して、mapping.Shipping()のようにクラスをマップする必要があります。

インポートマッピング#mapping.pyは私のモジュールファイルの名前です

shipit = mapping.Shipment()#Shipmentは、マッピングモジュールで使用する必要があるクラスの名前です

または、マッピングモジュールから特定のクラスをインポートします

マッピングインポートマッピングから

shipit = Shipment()#.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は、jupyterが起動時に.envファイルから環境変数をロードすることです。ダブルワーミー。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.