Python での循環インポートは混乱を招く可能性があります。間違えやすいので、できれば避けるのが通常はベストです。ただし、それらは違法ではなく、注意すれば問題なく機能します。
次のように入力すると、インポートプロセスは次のように機能しますimport foo
。
- まず、Python は
sys.modules
ディクショナリを調べて、モジュールfoo
が既に読み込まれているかどうかを確認します。その場合、既存のモジュールは単に現在のモジュールの名前空間に配置されます (プロセスはそこで停止します)。
- そうでない場合は、
foo.py
ロードするファイルが見つかるかどうかを確認します。存在しない場合は、ImportError
が発生し、プロセスが停止します。
- ファイルが見つかった場合、新しい空のモジュール オブジェクトが作成さ
sys.modules
れ、名前の下に配置されfoo
ます。
- 次に、Python はファイルを開き、手順 3 で作成したモジュール オブジェクトをローカル名前空間として使用して実行を開始します。
ここで、foo
上記のモジュールに独自のimport
ステートメントがある場合、インポート プロセスは、foo
モジュールのコードの実行を続行する前に、他のモジュールをロードして再帰できます。ただし、インポートが処理される前に空のモジュール オブジェクトがsys.modules
ディクショナリに追加されるため、完全にループして同じファイルを複数回ロードしようとすることはありません (ほとんどの場合)。
うまくいかないことがいくつかあります。
foo
が (他のモジュールによってインポートされるのではなく) スクリプトとして実行されている場合、 の最初のエントリはではなくsys.modules
名前の下になります。これは、他の場所からインポートされた場合、同じコードの 2 つのコピーが作成されることを意味します。これは、モジュールが 2 回読み込まれないという一般規則の例外であり、場合によっては不意を突かれる可能性があります。__main__
foo
foo
また、循環的にインポートされたモジュールが相互にトップレベルのアクセスを行っている場合、それらが間違った順序でインポートされた場合 (または任意の順序で、両方が互いのコンテンツを間違った方法で参照している場合) に問題が発生します。トップレベル)。モジュールのロード時に実行されるコードをできるだけ少なくすることで、ほとんどの問題を回避できます (代わりに関数に入れます!)。次に、すべてがロードされた後、明確に定義されたエントリ ポイントから関数を呼び出します。
したがって、あなたの状況では、ここに私が調べるものがあります:
- あなたがしている必要があります
from moduleB import foo
か?代わりに、後でimport moduleB
アクセスできますか? moduleB.foo
これは機能する最も簡単な修正です (ただし、考えられるすべての問題を修正するわけではありません)。
- モジュールの最上位で作業を行う必要がありますか? 多くの場合、オブジェクトの作成をトップレベルではなく、ファクトリ関数に入れることができます (これにより、ソリューション #1 がより頻繁に機能します)。実行時にオブジェクトを作成する方法を変更 (またはカスタマイズ) できるようにしたい場合は、後で拡張する方が簡単なので、これはとにかく良い設計アイデアです (別の方法で動作する 2 番目の関数を記述するか、追加するだけです)。ファクトリへの引数)。
- コードの一部を 3 番目のモジュールから、
ModuleA
またはModuleB
3 番目のモジュールに移動できますか? 3 番目のモジュールはいずれもインポートする必要がないため、循環インポートの必要がなくなりますか? これは、何がいつロードされるかという問題全体を回避できるため、多くの場合良いアイデアです。
編集: モジュールの固定バージョンは次のようになります。
ModuleA.py:
import ModuleB
class bar():
def __init__(self, data):
self.data = data
# do something here that accesses ModuleB.foo
ModuleB.py:
import ModuleA
def foo():
# do whatever
Array1 = [ModuleA.bar(i) for i in range(10)]
Array2 = [whatever]
main.py:
import ModuleB # import order is important here!
import ModuleA
if __name__ == "__main__":
# do stuff
これは最小限の修正です。ModuleAはクラスのメソッドModuleB.foo
内からのみアクセスするため、インスタンスが作成される前に定義されている限り、循環インポートは正常に機能します。bar
__init__
ModuleB.foo
bar
インポートを任意の順序で機能させたい場合は、少し注意が必要です。モジュールのトップレベルで作業を行う必要はありません。
ModuleA.py は上記の通りです。
ModuleB.py:
import ModuleA
def foo():
#do stuff
def setupArrays():
global Array1, Array2 # lets us create these global variables
Array1 = [ModuleA.bar(i) for i in range(10)]
Array2 = [whatever]
main.py:
import ModuleA, ModuleB # import order doesn't matter
if __name__ == "__main__":
ModuleB.setupArrays()
# do stuff
これはまだちょっとしたコツです。循環インポートを完全に解除できれば、おそらくコードをより明白な方法で単純化できます。たとえば、コールバック関数をパラメーターとしてbar
クラスのコンストラクターに渡すことができます。
ModuleA.py:
# No import statement here! Circular imports avoided!
class bar():
def __init__(self, data, callback):
self.data = data
# bind stuff to call the callback function provided
モジュール B:
import ModuleA
def foo():
# do whatever
Array1 = [ModuleA.bar(i, foo) for i in range(10)]
Array2 = [whatever]