Pythonでの循環(または循環)インポート


352

2つのモジュールが相互にインポートするとどうなりますか?

問題を一般化するために、Pythonの循環インポートについてはどうですか?



1
また、参考として、Python 3.5(およびそれ以降)では循環インポートが許可されているようですが、3.4(および以下)では許可されていません。
チャーリーパーカー

4
私はpython 3.7.2を使用していますが、依存関係が循環しているため、まだランタイムエラーが発生します。
Richard Whitehead

回答:


281

昨年comp.lang.pythonでこれについて本当に良い議論がありました。それはあなたの質問にかなり完全に答えます。

インポートは本当に簡単です。次のことを覚えておいてください。

'import'および 'from xxx import yyy'は実行可能なステートメントです。実行中のプログラムがその行に到達すると実行されます。

モジュールがsys.modulesにない場合、インポートによりsys.modulesに新しいモジュールエントリが作成され、モジュール内のコードが実行されます。実行が完了するまで、呼び出しモジュールに制御を返しません。

モジュールがsys.modulesに存在する場合、インポートは、実行が完了したかどうかに関係なく、単にそのモジュールを返します。これが、循環インポートが部分的に空のように見えるモジュールを返す可能性がある理由です。

最後に、実行中のスクリプトは__main__という名前のモジュールで実行され、独自の名前でスクリプトをインポートすると、__ main__とは無関係の新しいモジュールが作成されます。

この点をまとめると、モジュールをインポートするときに驚かないはずです。


13
@meawopplこのコメントを拡大していただけませんか?具体的にどのように変化しましたか?
Dan Schien

3
現在のところ、python3の循環インポートへの唯一の参照は「新機能」ですか?ページは3.5のものです。「相対インポートを含む循環インポートがサポートされるようになりました」とあります。@meawopplこれらのページに記載されていないものを他に見つけましたか?
zezollo

4
彼らはdefです。3.0-3.4ではサポートされていません。または、少なくとも成功のセマンティクスは異なります。3.5の変更点について触れていないことがわかりました。 gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

「最後に、実行中のスクリプトはmainという名前のモジュールで実行されます。独自の名前でスクリプトをインポートすると、mainとは無関係の新しいモジュールが作成されます。」を展開してください。それで、ファイルがa.pyであり、それがメインのエントリポイントとして実行されているときに、インポート変数などのコードがある場合は、そのメインがメインであるとしましょう。次に、同じファイル「a.py」がsysモジュールテーブルに読み込まれますか?それで、それがprintステートメントを持っている場合、2回実行されるということですか?メインファイルに対して1回、インポートが発生したときにもう一度ですか?
変数

この回答は10年前のものであり、Pythonのさまざまなバージョン、2.xまたは3.xでも正しいままであるように最新化された更新を希望します
Fallenreaper

296

import foo内部barimport bar内部fooで行う場合、それは正常に動作します。実際に何かが実行されるまでに、両方のモジュールが完全に読み込まれ、相互に参照されます。

代わりにあなたが行うときに問題があるfrom foo import abcfrom bar import xyz。これは、各モジュールで、インポートする前に他のモジュールをインポートする必要があるためです(インポートする名前が存在するため)。


27
それはそれだfrom foo import *from bar import *も罰金を動作します。
Akavall 2014年

1
a.py/b.pyを使用して上記の投稿の編集を確認してください。彼はを使用していませんfrom x import yが、それでも循環インポートエラーが発生します
Greg Ennis

2
これは完全に真実ではありません。import * fromと同様に、最上位で循環インポートの要素にアクセスしようとすると、スクリプトの実行が完了する前に、同じ問題が発生します。たとえば、あるパッケージのパッケージを別のパッケージにグローバルに設定し、それらのパッケージの両方が互いに含まれている場合などです。私はこれを行って、基本クラスのオブジェクトのずさんなファクトリーを作成しました。そのオブジェクトは多数のサブクラスの1つであり、使用するコードはそれが実際に作成しているものを認識する必要がありませんでした。
AaronM 2016

3
@Akavallそうではない。これにより、importステートメントの実行時に使用できる名前のみがインポートされます。したがって、エラーにはなりませんが、期待するすべての変数を取得できない場合があります。
オーグラ2016

3
注意、あなたが行う場合 from foo import *from bar import *、して実行すべてがfooの初期化段階にあるbar、との実際の機能をbarまだ定義されていない...
Martian2049

100

循環インポートは終了しますが、モジュールの初期化中に循環インポートされたモジュールを使用しないように注意する必要があります。

次のファイルを検討してください。

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

a.pyを実行すると、次のようになります。

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

b.pyの2番目のインポート(2番目のa in)では、Pythonインタープリターはbモジュールdictに既に存在するため、再度インポートされません。

あなたがアクセスしようとするb.xからa、モジュールの初期化中に、あなたが取得しますAttributeError

次の行をに追加しますa.py

print b.x

次に、出力は次のとおりです。

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

これは、モジュールがインポート時に実行されb.x、アクセス時にその行x = 3がまだ実行されていないためb outです。これはの後でのみ発生します。


14
これは問題を大きく説明していますが、解決策はどうですか?どのようにしてxを適切にインポートして印刷できますか?上記の他の解決策は私には
うまくいき

この答えは、の__name__代わりに使用した場合、多くのメリットがあると思います'a'。最初は、なぜファイルが2回実行されるのか完全に混乱していました。
ベルギ

30

他の回答がこのパターンを説明しているので、このパターンはPythonで受け入れられます:

def dostuff(self):
     from foo import bar
     ...

これにより、ファイルが他のモジュールによってインポートされたときに、インポートステートメントの実行が回避されます。論理的な循環依存関係がある場合のみ、これは失敗します。

ほとんどの循環インポートは実際には論理的な循環インポートではなく、呼び出されたときにファイル全体の最上位のステートメントを評価ImportErrorする方法が原因で、エラーを発生import()させます。

ImportErrorsあなたが積極的にあなたの輸入を望むならば、これらはほとんど常に避けられることができます

この循環インポートを検討してください:

アプリA

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

アプリB

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

David Beazleysによるすばらしいトークモジュールとパッケージ:Live and Let Die!- PyCon 20151:54:00、ここではPythonで、円形の輸入に対処する方法は次のとおりです。

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

これはインポートSimplifiedImageSerializerを試み、発生した場合ImportErrorはすでにインポートされているため、インポートキャッシュからプルします。

PS:この投稿全体をDavid Beazleyの声で読む必要があります。


9
モジュールがすでにインポートされている場合、ImportErrorは発生しません。モジュールは、「インポートa;インポートa;」のように何度でもインポートできます。大丈夫です。
ユラス、2016

9

私はここで私を襲った例を得ました!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

コマンドラインで: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

2
これをどのように修正しましたか?私は循環インポートを理解して、あなたがやっていることに非常によく似た自分の問題を修正しようとしています...
c089

12
えーと...この信じられないほど醜いハックで問題を解決したと思います。{{{sys.modulesの 'foo.bar'でない場合:fooインポートバーからelse else:bar = sys.modules ['foo.bar']}}}個人的には、循環インポートは不正なコードの巨大な警告サインであると思います設計...
c089

5
@ c089、またはあなただけ移動することができますimport barfoo.py終わりに
warvariuc

5
場合barfooの両方を使用しなければならないgX、「クリーンな」ソリューションを置くことであるgX別のモジュールで両方持っているfoobar、インポートモジュールのことを。(意味的な依存関係が隠されていないという意味で最もクリーンです。)
Tim Wilder

2
ティムは良い点を持っています。基本的にそれはfooでbarさえ見つけることができないからgXです。循環インポート自体は問題gXありませんが、インポート時に定義されていないだけです。
Martian2049 2017年

9

モジュールa.py:

import b
print("This is from module a")

モジュールb.py

import a
print("This is from module b")

「モジュールa」を実行すると、以下が出力されます。

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

循環インポートのために無限を出力するはずでしたが、この3行を出力しました。「モジュールa」の実行中に行ごとに何が発生するかを以下に示します。

  1. 最初の行はimport bです。モジュールbにアクセスします
  2. モジュールbの最初の行はimport aです。モジュールaを訪問します
  3. モジュールaの最初の行はですimport b、この行はもう実行されないことに注意してください。Pythonのすべてのファイルがインポート行を一度だけ実行するため、どこでいつ実行するかは問題ではありません。したがって、次の行に渡されて印刷されます"This is from module a"
  4. モジュールbからモジュールa全体を訪問した後も、モジュールbにいます。次の行が印刷されます"This is from module b"
  5. モジュールbの行は完全に実行されます。モジュールbを開始したモジュールaに戻ります。
  6. import b行はすでに実行されており、再度実行されることはありません。次の行が印刷され"This is from module a"、プログラムが終了します。

4

私はここでpythoneerの答えに完全に同意します。しかし、循環インポートに欠陥があり、単体テストを追加しようとすると問題が発生するコードに偶然遭遇しました。したがって、すべてを変更せずにパッチをすばやく適用するには、動的インポートを行うことで問題を解決できます。

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

繰り返しますが、これは恒久的な修正ではありませんが、コードをあまり変更せずにインポートエラーを修正したい場合に役立ちます。

乾杯!


3

ここにはたくさんの素晴らしい答えがあります。通常、問題に対する迅速な解決策がありますが、そのいくつかは他のものよりもPython的な感じがしますが、リファクタリングを実行する余裕がある場合は、別のアプローチは、コードの編成を分析し、循環依存関係を削除することです。たとえば、次のことがわかります。

ファイルa.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

ファイルb.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

この場合、1つの静的メソッドを別のファイルに移動するだけですc.py

ファイルc.py

def save_result(result):
    print('save the result')

除去可能になるsave_resultAからの方法を、したがってB内からAのインポートを除去可能。

リファクタリングされたファイルa.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

リファクタリングされたファイルb.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

要約すると、静的である可能性のあるメソッドについてレポートするツール(たとえば、pylintまたはPyCharm)がある場合、staticmethodそれらにデコレータをスローするだけでは警告を止める最善の方法ではない可能性があります。メソッドはクラスに関連しているように見えますが、特に同じ機能を必要とする可能性がある密接に関連するモジュールがいくつかあり、DRYの原則を実践するつもりである場合は、メソッドを分離する方が良い場合があります。


2

インポートは2つのことを行うため、循環インポートは混乱を招く可能性があります。

  1. インポートされたモジュールコードを実行します
  2. インポートされたモジュールをインポートモジュールのグローバルシンボルテーブルに追加します

前者は一度だけ実行され、後者は各インポート文で実行されます。循環インポートは、インポートモジュールが部分的に実行されたコードでインポートされたものを使用する場合に状況を作成します。その結果、インポート文の後に作成されたオブジェクトは表示されません。以下のコードサンプルはそれを示しています。

循環輸入は、いかなる犠牲を払っても避けられる究極の悪ではありません。Flaskのような一部のフレームワークでは、それらは非常に自然であり、それらを排除するためにコードを微調整しても、コードは改善されません。

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

コメント付きのpython main.py出力

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

1

以下の方法で問題を解決しましたが、問題なく動作しました。2つのファイルa.pyとを考えますb.py

これを追加したところa.py、うまくいきました。

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

私が得る出力は

>>> b out 
>>> a out 
>>> 5

0

わかりました、私にはかなりクールなソリューションがあると思います。file aとfile があるとしましょうb。あなたは持っているdefか、classファイルにbは、モジュールで使用することaができますが、何か他のものを持っている、のいずれかdefclassファイルからまたは変数aファイルでの定義やクラスに必要であることb。あなたはどうすることができ、ファイルの一番下に、あるaファイル内の関数やクラス呼び出した後、aファイルに必要とされbますが、ファイルから関数やクラスを呼び出す前にb、ファイルのために必要なことをa、言っimport b 次に、ここで重要な部分、ファイルからまたはファイルbを必要とするファイル内のすべての定義またはクラスdefclassa(それを呼び出しましょうCLASS)、あなたは言うfrom a import CLASS

これbは、Pythonがfile内のインポートステートメントを実行せずにファイルをインポートできるためb、循環インポートを回避できるため機能します。

例えば:

ファイルa:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

ファイルb:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

出来上がり。


from a import CLASSa.pyのすべてのコードの実行は実際にはスキップされません。これが実際に起こることです:(1)a.pyのすべてのコードは特別なモジュール「__main__」として実行されます。(2)ではimport b、b.pyの最上位コードが実行され(クラスBを定義)、制御が「__main__」に戻ります。(3)「__main__」は最終的に制御をに渡しgo.dostuff()ます。(4)dostuff()がになるとimport a、すべてのコードがa.pyで再び実行されます。今回はモジュール "a"として実行されます。次に、新しいモジュール「a」からCLASSオブジェクトをインポートします。したがって、実際には、これはimport ab.pyのどこかで使用した場合と同じように機能します。
Matthias Fripp
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.