Python循環インポート?


98

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

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

そして、私は同じimportステートメントをさらに使用して、それが機能することを確認できますか?循環インポートについての不文律はありますか?コールスタックのさらに下で同じクラスを使用するにはどうすればよいですか?

回答:


161

jpmc26の答えは間違いないと思います、循環インポートではあまりにも多くのことになります。正しく設定すれば、問題なく動作します。

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

あなたのケースに特化するために:直接変更entities/post.pyするのではなく、変更してimport physicsから参照してみてください。同様に、変更するだけでなく、使用してから使用します。physics.PostBodyPostBodyphysics.pyimport entities.postentities.post.PostPost


5
この回答は相対インポートと互換性がありますか?
Joe

17
なぜこれが起こるのですか?
ファンパブロサントス

4
from構文は常に機能するというのは誤りです。私が持っている場合はclass A(object): pass; class C(b.B): pass、モジュールAにし、class B(a.A): passモジュールBに続い円形インポートはまだ問題があり、これは動作しません。
CrazyCasta 2015

1
そうです、モジュールのトップレベルコードの循環依存関係(例のクラス宣言の基本クラスなど)は問題になります。これは、モジュール構成をリファクタリングする必要があるというjpmcの答えがおそらく100%正しいような状況です。クラス移動のいずれかのBモジュールにa、または移動クラスをCモジュールにbあなたが悪循環を断ち切ることができるように。サークルの1つの方向のみにトップレベルのコードが含まれている場合でも(クラスCが存在しない場合など)、他のコードによって最初にインポートされたモジュールによっては、エラーが発生する可能性があることにも注意してください。
Blckknght 2015

2
@TylerCrompton:「モジュールのインポートは絶対的でなければならない」という意味がわかりません。内容ではなくモジュールをインポートしている限り、循環相対インポートは機能します(例:from . import sibling_moduleでないfrom .sibling_module import SomeClass)。パッケージの__init__.pyファイルが循環インポートに関与している場合はさらに微妙ですが、この問題はまれであり、おそらくimport実装のバグです。私がパッチを提出したPythonバグ23447を参照してください(悲しいことに、これは悲惨です)。
Blckknght 2016年

51

モジュール(またはそのメンバー)を初めてインポートすると、モジュール内のコードは他のコードと同様に順次実行されます。たとえば、関数の本体と同じように扱われます。アンはimport、他の(代入、関数コール、などのコマンドだけでdefclass)。インポートがスクリプトの先頭で発生すると想定すると、次のようになります。

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

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


1
する必要がありますisinstance(userData, Post)。とにかく、選択肢はありません。循環インポートは機能しません。循環インポートがあるという事実は、コードの匂いです。3番目のモジュールに移動する必要があるいくつかの機能があることを示しています。両方のクラス全体を見なければ、私は何も言えませんでした。
jpmc26 2014年

3
@CpILLしばらくすると、非常にハックなオプションが私に起こりました。現時点でこれを回避できない場合は(時間の制約や何が原因か)、それを使用しているメソッド内でローカルにインポートできます。内部の関数本体defは関数が呼び出されるまで実行されないため、実際に関数を呼び出すまでインポートは行われません。それまでimportに、モジュールの1つが呼び出しの前に完全にインポートされているため、は機能するはずです。これは絶対に嫌なハックであり、かなり長い間コードベースに留まってはなりません。
jpmc26 2014年

15
循環輸入の場合、答えは厳しすぎると思います。循環インポートは通常、import fooではなく行う場合に機能しますfrom foo import Bar。これは、ほとんどのモジュールが、後で実行されるもの(関数やクラスなど)を定義するだけだからです。インポート時に重要なことを行うモジュール(で保護されていないスクリプトなどif __name__ == "__main__")も問題が発生する可能性がありますが、それほど一般的ではありません。
Blckknght 2014年

6
@Blckknght私は、循環インポートを使用する場合、他の人々が調査し、混乱する必要がある奇妙な問題に時間を費やす準備をしていると思います。それらはあなたがそれらをつまずかないように注意して時間を費やすことを強制し、その上にあなたのデザインがリファクタリングを必要とするコードの匂いがあります。それらが技術的に実現可能であるかどうかについて私は間違っていたかもしれませんが、遅かれ早かれ問題を引き起こす運命にある恐ろしい設計の選択です。明快さと単純さはプログラミングの神聖な障害であり、循環インポートは私の本の両方で違反しています。
jpmc26 14年

6
あるいは; 機能の分割が多すぎ、それが循環インポートの原因です。常に相互に依存している2つのことがある場合、それらを1つのファイルに入れるのが最善です。PythonはJavaではありません。奇妙なインポートロジックを防ぐために機能/クラスを1つのファイルにグループ化しない理由はありません。:-)
Mark Ribau、2015年

40

循環依存関係を理解するには、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)

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


2
Pythonにはコンパイラーまたはコンパイル時間がまったくないと思います
pkqxdd '

6
Pythonはありませんコンパイラを持っている、とされて @pkqxddをコンパイルし、コンパイルはちょうど通常、ユーザーから隠されています。これは少し混乱するかもしれませんが、Pythonのややあいまいな「コンパイル時間」を参照せずに、何が起こっているのかをこの見事に明確に説明することは、作成者にとって難しいでしょう。
ハンク


私は自分のマシンでこれを試してみましたが、異なる結果が得られました。X.pyを実行しましたが、「「Y」から名前「Y2」をインポートできません」というエラーが発生しました。Y.pyを問題なく実行した。私はPython 3.7.5を使用しています。ここで問題の説明を手伝っていただけますか?
xuefeng huang

18

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

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

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

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


6
コメントを使って「ありがとう」を言うつもりはないのはわかっていますが、これは数時間悩まされてきました。ありがとう、ありがとう、ありがとう!!!
MikeyE 2018

@MikeyEに同意します。PonyORMでこれを改善しようとするいくつかのブログとStackoverflowsを読んだことがあります。他の人が悪い習慣だと言ったり、クラスを循環型にコード化したりする理由は何でしょうか。ORMはまさにこれが発生する場所です。多くの例ではすべてのモデルを同じファイルに入れ、ファイルごとにモデルを使用することを除いて、これらの例に従っているため、Pythonがコンパイルに失敗した場合の問題は明確ではありません。しかし、答えはとても簡単です。マイクが指摘したように、どうもありがとうございました。
trash80

3

このモジュールのオブジェクトを必要とする関数(のみ)内にモジュールをインポートすることができました。

def my_func():
    import Foo
    foo_instance = Foo()

Pythonのエレガントさ
Yaro

2

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

ここに画像の説明を入力してください


0

私は以下を使用していました:

from module import Foo

foo_instance = Foo()

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

import module.foo

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