web-dev-qa-db-ja.com

Pythonで循環インポートを避ける方法は?

pythonでの循環インポートの問題は以前にも何度も出てきました。これらの議論を読んだことがあります。これらの議論で繰り返し行われているコメントは、循環インポートは循環インポートを避けるために、設計が不適切であり、コードを再編成する必要があります。

この状況で循環インポートを回避する方法を教えてもらえますか?:2つのクラスがあり、各クラスに他のクラスのインスタンスを取り、クラスのインスタンスを返すコンストラクター(メソッド)が必要です。

より具体的には、1つのクラスは可変で、もう1つのクラスは不変です。不変クラスは、ハッシュ、比較などに必要です。可変クラスは、物事を行うためにも必要です。これは、setsやfrozensets、またはリストやタプルに似ています。

同じモジュールに両方のクラス定義を配置できます。他に提案はありますか?

おもちゃの例は、リストである属性を持つクラスAと、タプルである属性を持つクラスBです。次に、クラスAにはクラスBのインスタンスを取り、クラスAのインスタンスを返す(タプルをリストに変換する)メソッドがあり、同様にクラスBにはクラスAのインスタンスを取り、クラスBのインスタンスを返すメソッドがあります(リストをタプルに変換することにより)。

92
BWW

モジュールのみをインポートし、モジュールからインポートしないでください:

a.py

import b

class A:
    def bar(self):
        return b.B()

およびb.py

import a

class B:
    def bar(self):
        return a.A()

これは完璧に機能します。

86
rumpel

次の例を考えてください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インポート機構の明白なバグのようですが、それは私の意見です。

152
Brendan Abel

読み取りを改善し、アクセス文字列を短くするために、絶対インポートと関数の組み合わせを行います。

  • 利点:純粋な絶対インポートと比較してアクセス文字列が短い
  • 欠点:余分な関数呼び出しによるオーバーヘッドが少し増えます

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__)
6