pythonでの循環インポートの問題は以前にも何度も出てきました。これらの議論を読んだことがあります。これらの議論で繰り返し行われているコメントは、循環インポートは循環インポートを避けるために、設計が不適切であり、コードを再編成する必要があります。
この状況で循環インポートを回避する方法を教えてもらえますか?:2つのクラスがあり、各クラスに他のクラスのインスタンスを取り、クラスのインスタンスを返すコンストラクター(メソッド)が必要です。
より具体的には、1つのクラスは可変で、もう1つのクラスは不変です。不変クラスは、ハッシュ、比較などに必要です。可変クラスは、物事を行うためにも必要です。これは、setsやfrozensets、またはリストやタプルに似ています。
同じモジュールに両方のクラス定義を配置できます。他に提案はありますか?
おもちゃの例は、リストである属性を持つクラスAと、タプルである属性を持つクラスBです。次に、クラスAにはクラスBのインスタンスを取り、クラスAのインスタンスを返す(タプルをリストに変換する)メソッドがあり、同様にクラスBにはクラスAのインスタンスを取り、クラスBのインスタンスを返すメソッドがあります(リストをタプルに変換することにより)。
モジュールのみをインポートし、モジュールからインポートしないでください:
a.py
:
import b
class A:
def bar(self):
return b.B()
およびb.py
:
import a
class B:
def bar(self):
return a.A()
これは完璧に機能します。
次の例を考えてくださいpython package where a.py
およびb.py
相互依存している:
/package
__init__.py
a.py
b.py
Pythonでモジュールをインポートするにはいくつかの方法があります
import package.a # Absolute import
import package.a as a_mod # Absolute import bound to different name
from package import a # Alternate absolute import
import a # Implicit relative import (deprecated, py2 only)
from . import a # Explicit relative import
残念ながら、循環依存がある場合、実際に機能するのは1番目と4番目のオプションのみです(残りはすべてImportError
またはAttributeError
を発生させます)。一般に、4番目の構文はpython2でのみ機能し、他のサードパーティモジュールと競合するリスクがあるため、4番目の構文は使用しないでください。したがって、実際には、最初の構文のみが機能することが保証されています。ただし、循環依存関係を処理する場合、いくつかのオプションがあります。
編集:
ImportError
およびAttributeError
の問題は、python 2.でのみ発生します。python 3でインポート機械が書き直されました。そして、これらのすべてのインポートステートメント(4を除く)は、循環依存関係があっても機能します。
上記の最初のインポート構文を使用してください。この方法の欠点は、大きなパッケージの場合、インポート名がsuper longを取得できることです。
a.py
import package.b
b.py
import package.a
私はこの方法が多くのパッケージで使用されているのを見てきましたが、それでも私にはハッキングを感じます。また、モジュールの上部を見てその依存関係をすべて見ることができないので、すべての機能を検索する必要があります同じように。
a.py
def func():
from package import b
b.py
def func():
from package import a
これも機能しますが、すべてのパッケージとサブモジュールの呼び出しがsuper longを取得する最初の方法と同じ問題があります。また、2つの大きな欠陥があります-強制的にすべてのサブモジュールをインポートします。1つまたは2つだけを使用している場合でも、サブモジュールのいずれも見ることができず、すぐに見ることができます最上位の依存関係は、関数をふるいにかける必要があります。
__init__.py
from . import a
from . import b
a.py
import package
def func():
package.b.some_object()
b.py
import package
def func():
package.a.some_object()
したがって、これらはあなたのオプションです(そして、それらはすべてIMOを吸います)。率直に言って、これはpythonインポート機構の明白なバグのようですが、それは私の意見です。
読み取りを改善し、アクセス文字列を短くするために、絶対インポートと関数の組み合わせを行います。
main/sub/a.py
import main.sub.b
b_mod = lambda: main.sub.b
class A():
def __init__(self):
print('in class "A":', b_mod().B.__name__)
main/sub/b.py
import main.sub.a
a_mod = lambda: main.sub.a
class B():
def __init__(self):
print('in class "B":', a_mod().A.__name__)