web-dev-qa-db-ja.com

循環インポートステートメントを解決するPythonの方法?

私が不安を感じるコードを継承しただけです。テストライブラリがあり、サイトのWebページに対応するクラスがたくさんあります。各Webページクラスには、そのページの機能を自動化するメソッドがあります。

リンクされたページのクラスを返す、ページ間のリンクをクリックするメソッドがあります。簡単な例を次に示します。

ファイルhomePageLib.py:

class HomePage(object):
    def clickCalendarLink(self):
        # Click page2 link which navigates browswer to page2
        print "Click Calendar link"
        # Then returns the page2 object
        from calendarLib import CalendarPage
        return CalendarPage()

ファイルcalendarLib.py:

class CalendarPage(object):
    def clickHomePageLink(self):
        # Click page1 link which navigates browswer to page1
        print "Click Home Page link"
        # Then return the page2 object
        from homePageLib import HomePage
        return HomePage()

これにより、スクリプトファイルがページをクリックし、そのメソッドからの戻り値としてオブジェクトを取得できるようになります。つまり、スクリプトの作成者は、サイト内を移動するときに新しいページをインスタンス化し続ける必要はありません。 (これは奇妙なデザインのように感じますが、なぜ「clickSomeLink」という名前のメソッドがあり、結果のページのオブジェクトを返すのが奇妙に見えることを除いて、理由を正確に言えません。)

次のスクリプトは、スクリプトがサイト内を移動する方法を示しています(ページオブジェクトの変化を示すためにprint pageを挿入しました)。

スクリプトファイル:

from homePageLib import HomePage

page = HomePage()    
print page
page = page.clickCalendarLink()
print page
page = page.clickHomePageLink()
print page

次の出力が生成されます。

<homePageLib.HomePage object at 0x00B57570>
Click Calendar link
<calendarLib.CalendarPage object at 0x00B576F0>
Click Home Page link
<homePageLib.HomePage object at 0x00B57570>

だから、私が特に最も心配しているこの部分は、結局終わるfrom ____ import ____行です。これらは、以下の理由で私をひどく傷つけます:

  1. 私は常に、すべてのインポート文をファイルの先頭に置くことを習慣にしています。
  2. ページへのリンクが複数ある可能性があるため、ファイルの複数の場所に同じfrom foo import barコード行が生成されます。

問題は、これらのインポートステートメントをページの上部に配置すると、インポートエラーが発生することです(この例のように)、HomePageはCalendarPageをインポートし、その逆も同様です。

ファイルhomePageLib.py

from calendarLib import CalendarPage

class HomePage(object):
    def clickCalendarLink(self):
        # Click page2 link which navigates browswer to page2
        print "Click Calendar link"
        # Then returns the page2 object

        return CalendarPage()

ファイルcalendarLib.py

from homePageLib import HomePage

class CalendarPage(object):
    def clickHomePageLink(self):
        # Click page1 link which navigates browswer to page1
        print "Click Home Page link"
        # Then return the page2 object
        return HomePage()

これにより、次のエラーが発生します。

>>> from homePageLib import HomePage
Traceback (most recent call last):
  File "c:\temp\script.py", line 1, in ?
    #Script
  File "c:\temp\homePageLib.py", line 2, in ?
    from calendarLib import CalendarPage
  File "c:\temp\calendarLib.py", line 2, in ?
    from homePageLib import HomePage
ImportError: cannot import name HomePage

(より適切なフォーマット方法のヒントpython出力?)

このスタイルを永続させるのではなく、もっと良い方法を見つけたいです。このような循環依存関係を処理し、インポート文をファイルの先頭に保持するPython的な方法はありますか?

35
Nathan

これらの構成要素の解決には、通常 Dependency Injection のような手法が含まれます。

ただし、このエラーを修正するのはかなり簡単です。

CalendarLib.py内:

import homePageLib

class CalendarPage(object):
    def clickHomePageLink(self):
        [...]
        return homePageLib.HomePage()

モジュールレベルのコードはインポート時に実行されます。 from [...] import [...]構文を使用するには、成功するためにモジュールを完全に初期化する必要があります。

単純なimport [...]はアクセスしません。これは、シンボルにアクセスしないため、依存関係チェーンが壊れるためです。

66
ebo

詳細な説明については セバスチャンの答え を読んでください。このアプローチは、David Beazleyによって PyCon で提案されました。

このようにインポートを上部に配置してみてください

try:
    from homePageLib import HomePage
except ImportError:
    import sys
    HomePage = sys.modules[__package__ + '.HomePage']

これはHomePageをインポートしようとし、失敗した場合はキャッシュからロードしようとします

3
Kashif Siddiqui