web-dev-qa-db-ja.com

Python循環インポート?

だから私はこのエラーを取得しています

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

そして、同じインポートステートメントをさらに上に使用すると動作することがわかりますか?循環インポートに関して書かれていないルールはありますか?同じクラスを呼び出しスタックのさらに下で使用するにはどうすればよいですか?

85
CpILL

Jpmc26による答えは、決してwrongではありませんが、循環インポートではあまりにも大きく低下すると思います。正しく設定すれば、問題なく機能します。

これを行う最も簡単な方法は、import my_moduleではなくfrom my_module import some_object構文を使用することです。前者は、my_moduleが含まれているとインポートされる場合でも、ほとんど常に機能します。後者は、my_objectmy_moduleですでに定義されている場合にのみ機能します。これは、循環インポートでは当てはまらない場合があります。

特定のケースに合わせて:entities/post.pyを変更してimport physicsを実行し、PostBodyだけでなくphysics.PostBodyを直接参照してください。同様に、physics.pyを変更してimport entities.postを実行し、Postだけでなくentities.post.Postを使用します。

126
Blckknght

モジュール(またはそのメンバー)を初めてインポートすると、モジュール内のコードは他のコードと同様に順番に実行されます。たとえば、関数の本体とまったく異なる扱いはされません。 importは、他のすべてのコマンド(割り当て、関数呼び出し、defclass)と同じです。インポートがスクリプトの上部で行われると仮定すると、次のことが起こります。

  • Worldからworldをインポートしようとすると、worldスクリプトが実行されます。
  • worldスクリプトはFieldをインポートします。これにより、entities.fieldスクリプトが実行されます。
  • このプロセスは、Postをインポートしようとしたため、entities.postスクリプトに到達するまで続きます。
  • entities.postスクリプトは、physicsをインポートしようとするため、PostBodyモジュールを実行します。
  • 最後に、physicsentities.postからPostをインポートしようとします
  • entities.postモジュールがまだメモリに存在するかどうかはわかりませんが、実際には問題ではありません。モジュールがメモリにないか、モジュールにまだPostメンバーがありませんPostを定義するための実行が終了していません
  • いずれにしても、Postがインポートされるために存在しないため、エラーが発生します

いいえ、それは「コールスタックのさらに上への作業」ではありません。これは、エラーが発生した場所のスタックトレースです。つまり、そのクラスでPostをインポートしようとしてエラーが発生しました。循環インポートを使用しないでください。せいぜい、無視できる利益(通常、no利益)があり、このような問題を引き起こします。それを維持する開発者に負担をかけ、卵殻を壊さないように強制的に卵の殻の上を歩かせます。モジュール組織をリファクタリングします。

47
jpmc26

循環依存関係を理解するには、Pythonが本質的にスクリプト言語であることを覚えておく必要があります。メソッド外のステートメントの実行はコンパイル時に発生します。インポート文はメソッド呼び出しのように実行されます。それらを理解するには、メソッド呼び出しのように考える必要があります。

インポートを行うとき、インポートするファイルが既にモジュールテーブルに存在するかどうかによって異なります。存在する場合、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のimportステートメントをヒットします。 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を定義する前にimportステートメントをヒットします。次に、X.pyのコンパイルを開始します。すぐに、Y2を必要とするX.pyのimportステートメントにヒットします。ただし、Y2は未定義であるため、コンパイルは失敗します。

X.pyを変更してY1をインポートする場合、どのファイルをコンパイルしても、コンパイルは常に成功することに注意してください。ただし、ファイルY.pyを変更してシンボルX2をインポートすると、どちらのファイルもコンパイルされません。

モジュールX、またはXによってインポートされたモジュールが現在のモジュールをインポートする可能性がある場合は、以下を使用しないでください。

from X import Y

循環インポートがあると思われるときはいつでも、コンパイル時の他のモジュールの変数への参照も避けるべきです。無邪気な見た目のコードを考えてみましょう。

import X
z = X.Y

このモジュールがXをインポートする前にモジュールXがこのモジュールをインポートするとします。さらに、importステートメントの後にXでYが定義されているとします。このモジュールがインポートされると、Yは定義されず、コンパイルエラーが発生します。このモジュールが最初にYをインポートする場合、それを回避できます。しかし、同僚の1人が3番目のモジュールの定義の順序を無邪気に変更すると、コードが破損します。

場合によっては、importステートメントを他のモジュールで必要なシンボル定義の下に移動することにより、循環依存関係を解決できます。上記の例では、importステートメントの前の定義が失敗することはありません。 importステートメントの後の定義は、コンパイルの順序によっては失敗する場合があります。インポートされたシンボルがコンパイル時に必要ない限り、インポート文をファイルの最後に置くこともできます。

モジュール内でimportステートメントを下に移動すると、何をしているのかわかりにくくなることに注意してください。モジュールの上部に次のようなコメントを追加して、これを補います。

#import X   (actual import moved down to avoid circular dependency)

一般的にこれは悪い習慣ですが、回避するのが難しい場合もあります。

30
Gene Olson

私のように、Djangoからこの問題に出くわした方は、ドキュメントが解決策を提供していることを知っておく必要があります。 https://docs.djangoproject.com/en/1.10/ref/models/fields/ #foreignkey

「...別のアプリケーションで定義されたモデルを参照するには、完全なアプリケーションラベルでモデルを明示的に指定できます。たとえば、上記のメーカーモデルがproductionと呼ばれる別のアプリケーションで定義されている場合、以下を使用する必要があります:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

この種の参照は、2つのアプリケーション間の循環インポート依存関係を解決するときに役立ちます。... "

12
Malik A. Rumi

かなり複雑なアプリでこの問題に遭遇した場合、すべてのインポートをリファクタリングするのは面倒です。 PyCharmは、インポートされたシンボルのすべての使用方法を自動的に変更するためのクイックフィックスを提供します。

enter image description here

2

私は次を使用していました:

from module import Foo

foo_instance = Foo()

しかし、circular referenceを取り除くために、私は次のことをしましたが、うまくいきました:

import module.foo

foo_instance = foo.Foo()
0
MKJ