循環依存関係を理解するには、Pythonが基本的にスクリプト言語であることを覚えておく必要があります。メソッド外のステートメントの実行はコンパイル時に行われます。Importステートメントはメソッド呼び出しと同じように実行されます。それらを理解するには、メソッド呼び出しのように考える必要があります。
インポートを行う場合、何が起こるかは、インポートするファイルがモジュールテーブルに既に存在するかどうかによって異なります。存在する場合、Pythonは現在シンボルテーブルにあるものを使用します。そうでない場合、Pythonはモジュールファイルの読み取りを開始し、そこで見つかったものをコンパイル、実行、インポートします。コンパイル時に参照されたシンボルは、コンパイラによって表示されたか、まだ表示されていないかによって、検出されるかどうかが決まります。
2つのソースファイルがあるとします。
ファイルX.py
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
ファイルY.py
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
ここで、ファイルX.pyをコンパイルするとします。コンパイラは、まずメソッドX1を定義してから、X.pyのインポートステートメントをヒットします。これにより、コンパイラーはX.pyのコンパイルを一時停止し、Y.pyのコンパイルを開始します。その後すぐに、コンパイラーはY.pyのインポートステートメントにヒットします。X.pyはすでにモジュールテーブルにあるため、Pythonは既存の不完全なX.pyシンボルテーブルを使用して、要求されたすべての参照を満たします。X.pyのimportステートメントの前に現れるシンボルはすべてシンボルテーブルにありますが、その後のシンボルはありません。X1はimportステートメントの前に表示されるので、正常にインポートされます。次に、PythonはY.pyのコンパイルを再開します。そうすることで、Y2を定義し、Y.pyのコンパイルを終了します。次に、X.pyのコンパイルを再開し、Y.pyシンボルテーブルでY2を見つけます。コンパイルは最終的にエラーなしで完了します。
コマンドラインからY.pyをコンパイルしようとすると、非常に異なることが起こります。Y.pyのコンパイル中に、コンパイラーはY2を定義する前にインポートステートメントにヒットします。次に、X.pyのコンパイルを開始します。すぐに、Y2を必要とするX.pyのインポートステートメントにヒットします。しかし、Y2は未定義なので、コンパイルは失敗します。
X.pyを変更してY1をインポートする場合、どのファイルをコンパイルしても、コンパイルは常に成功することに注意してください。ただし、ファイルY.pyを変更してシンボルX2をインポートすると、どちらのファイルもコンパイルされません。
モジュールX、またはXによってインポートされたモジュールが現在のモジュールをインポートする可能性がある場合は常に、以下を使用しないでください。
from X import Y
循環インポートがあると思うときはいつでも、他のモジュールの変数へのコンパイル時参照を避ける必要があります。無邪気に見えるコードを考えてみましょう:
import X
z = X.Y
このモジュールがXをインポートする前に、モジュールXがこのモジュールをインポートするとします。さらに、Yがimportステートメントの後のXで定義されているとします。そうすると、このモジュールがインポートされるときにYは定義されず、コンパイルエラーが発生します。このモジュールが最初にYをインポートする場合は、それで問題ありません。しかし、同僚の1人が無邪気に3番目のモジュールの定義の順序を変更すると、コードが壊れます。
場合によっては、他のモジュールが必要とするシンボル定義の下にインポート文を移動することにより、循環依存関係を解決できます。上記の例では、importステートメントの前の定義が失敗することはありません。コンパイル順によっては、インポート文以降の定義が失敗する場合があります。インポートされたシンボルがコンパイル時に必要とされない限り、インポートステートメントをファイルの最後に置くこともできます。
モジュール内でimportステートメントを下に移動すると、実行していることがわかりにくくなることに注意してください。モジュールの上部にある次のようなコメントでこれを補正します。
#import X (actual import moved down to avoid circular dependency)
一般にこれは悪い習慣ですが、回避するのが難しい場合もあります。