web-dev-qa-db-ja.com

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

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

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

292
Xolve

これについては昨年 comp.lang.python で非常に良い議論がありました。それはあなたの質問にかなり徹底的に答えます。

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

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

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

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

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

その多くを一緒に取り込んで、モジュールをインポートするときに驚くことはないはずです。

245
Shane C. Mason

bar内でimport fooを実行し、foo内でimport barを実行すると、正常に動作します。実際に何かが実行されるまでに、両方のモジュールは完全にロードされ、相互に参照します。

問題は、代わりにfrom foo import abcfrom bar import xyzを行うときです。なぜなら、各モジュールは、インポートする前に(インポートする名前が存在するように)既にインポートされている他のモジュールを必要とするからです。

246
pythoneer

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

以下のファイルを考慮してください。

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

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

モジュールの初期化中にaからb.xにアクセスしようとすると、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の後にのみ発生します。

95
Torsten Marek

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

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

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

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

これらの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が既にインポートされているために発生した場合、それはimportcacheからプルされます。

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

25
Sebastian Wozny

ここで私に衝撃を与えた例がありました!

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
8
Xolve

私はここでの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")

繰り返しますが、これは永続的な修正ではありませんが、コードをあまり変更せずにインポートエラーを修正したい人を助けるかもしれません。

乾杯!

4
radtek

モジュール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
Mohsen Haddadi

次の方法で問題を解決しましたが、エラーなく正常に機能します。 2つのファイルa.pyb.pyを考えてください。

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

if __== "__main__":
        main ()

a.py:

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

if __== "__main__":
    main ()

b.py:

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

私が得る出力は

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

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

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

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

循環輸入は、いかなる犠牲を払っても回避される究極の悪ではありません。 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 __== '__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, __= {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __= {}'.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, __= b    # b code execution started
b imports a
a in, __= 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
Jacek Błocki