Python 循環インポート

Salman Mehmood 2023年6月21日
  1. Python での循環インポート エラーの再現
  2. Python で循環インポート エラーを解決する
  3. Python で型ヒントが原因で発生する循環インポート エラーを解決する
Python 循環インポート

この記事では、循環インポートが一般的にどのように発生するか、およびそれらを修正する主な方法について説明します。 また、Python のタイプ ヒントが原因で発生する循環インポート エラーを修正する方法についても説明します。

Python での循環インポート エラーの再現

モジュールをインポートしようとしたときにエラーに遭遇したことがありますか? この場合、以下に示すエラーが発生します。

from My_Module_A import My_FUNC_A


def My_main_Func():
    My_FUNC_A()


if __name__ == "main":
    My_main_Func()

出力:

ImportError: cannot import name 'My_FUNC_A' from partially initialized module 'My_Module_A' (most likely due to a circular import)

最も可能性の高い原因は循環インポートであることがわかります。 この種のインポート サイクルが実行時に発生する可能性がある最も単純で最も一般的な方法は次のとおりです。

python 循環インポート - 例 1

そのため、My_Module_A の初期化と My_Module_A からのコードの実行が開始されますが、My_Module_A の最初の行は、モジュール MY_Module_B から何かをインポートすることです。

MY_Module_B を最初に初期化する必要があるため、モジュール My_Module_A の初期化を停止します。 そして、MY_Module_B にホップして、代わりにそのコードの実行を開始します。

しかし、モジュール MY_Module_B の最初の行は My_Module_A からの何かが必要なので、MY_Module_B の実行を停止し、My_Module_A に移動します。

そして、My_Module_A の初期化を開始したいのですが、My_Module_A が既に初期化プロセスにあることに気付きます。 そこでエラーが発生します。

この一連のイベントがトレースバックで展開されていることがわかります。 ここでは、main.py を参照してください。 モジュール My_Module_A から関数 My_FUNC_A をインポートしようとします。これにより、モジュール MY_Module_B から関数 MY_Func_B2 がインポートされます。

そして、MY_Module_B.py はモジュール My_Module_A から再び関数 My_FUNC_A のインポートをトリガーし、最終的なエラーが発生しました。

Traceback (most recent call last):
  File "c:\Users\Dell\Desktop\demo\main.py", line 1, in <module>
    from My_Module_A import My_FUNC_A
  File "c:\Users\Dell\Desktop\demo\My_Module_A.py", line 1, in <module>
    from MY_Module_B, import MY_Func_B2
  File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 1, in <module>
    from My_Module_A import My_FUNC_A
ImportError: cannot import name 'My_FUNC_A' from partially initialized module 'My_Module_A' (most likely due to a circular import) (c:\Users\Dell\Desktop\demo\My_Module_A.py)

モジュールMy_Module_AMY_Module_Bの間にインポート時間サイクルがありますが、インポートした名前を見ると、インポート時にそれらを使用していません。

この関数 My_FUNC_A() またはこの関数 MY_Func_B2() が呼び出されるときに、それらを使用します。 インポート時にこれらの関数を呼び出していないため、真の循環依存関係はありません。つまり、このインポート サイクルを排除できます。

Python で循環インポート エラーを解決する

この問題を解決する一般的な方法は 3つあります。 1つ目は、インポートを取得して関数内に配置することです。

Python では、関数が呼び出されたときを含め、インポートはいつでも発生する可能性があります。 この関数 MY_Func_B1() が必要な場合は、My_FUNC_A() 関数内のインポートである可能性があります。

def My_FUNC_A():
    from MY_Module_B import MY_Func_B1

    MY_Func_B1()

関数 My_FUNC_A() をまったく呼び出さない場合は、MY_Func_B1 をインポートする必要さえない可能性があるため、さらに効率的である可能性があります。 たとえば、My_Module_A に多数の異なる関数があり、多くの異なる関数がこの関数 MY_Func_B1() を使用しているとします。

その場合、モジュール MY_Module_B を直接インポートし、それを使用する関数で MY_Module_B.MY_Func_B1() のような長い名前を使用することをお勧めします。 したがって、サイクルに含まれるすべてのモジュールに対してこの変換を行ってください。

import MY_Module_B


def My_FUNC_A():
    MY_Module_B.MY_Func_B1()

これにより、実行時エラーのインポート サイクルがどのように解決されるかを見てみましょう。 まず、My_main_Func() 関数は My_Module_A から何かをインポートしようとし、My_Module_A の実行を開始します。 次に、My_Module_A.py でモジュール MY_Module_B をインポートします。これにより、MY_Module_B の実行が開始されます。

MY_Module_B.py では、インポート モジュール My_Module_A に到達すると、モジュールは既に初期化を開始しています。 このモジュール オブジェクトは技術的に存在します。 存在するので、これを再実行しません。

存在するというだけなので、MY_Module_B モジュールは続行してインポートを終了し、そのインポートが完了すると、My_Module_A モジュールのインポートが終了します。

python 循環インポート - 例 2

出力:

15

このインポート サイクルをなくす 3 番目で最後の一般的な方法は、2つのモジュールをマージすることです。 繰り返しになりますが、インポートもインポート サイクルもありません。 ただし、最初に 2つまたはそれ以上のモジュールがあった場合は、おそらく何らかの理由でそれらを持っていたでしょう。

すべてのモジュールを 1つにマージすることを推奨するだけではありません。 それはばかげているでしょう。 したがって、おそらく最初の 2つの方法のいずれかを使用することをお勧めします。

My_Module_A.py ファイルのソース コード。

import MY_Module_B


def My_FUNC_A():
    print(MY_Module_B.MY_Func_B1())

MY_Module_B.py ファイルのソース コード。

import My_Module_A


def MY_Func_B1():
    return 5 + 10


def MY_Func_B2():
    My_Module_A.My_FUNC_A()

main.py ファイルのソース コード。

from My_Module_A import My_FUNC_A


def My_main_Func():
    My_FUNC_A()


if __name__ == "main":
    My_main_Func()

Python で型ヒントが原因で発生する循環インポート エラーを解決する

別の例を見てみましょう。タイプ ヒントを使用するために実行する、次の最も一般的な種類のインポート サイクルです。 この例は、型注釈が関数またはクラス定義で定義されているため、前の例とは異なります。

関数が定義されている場合、またはクラスが定義されているが実行時に定義されていない場合、型ヒントの最も一般的な使用例は実行時でもありません。 静的解析だけを考えれば、実行時にインポートを行う必要さえありません。

typing モジュールはこの変数を提供します。TYPE_CHECKING は実行時の false 定数です。 したがって、型チェックに必要なすべてのインポートをこれらのいずれかに入れると、実行時に何も起こりません。

インポート ループを完全に回避して、このクラス Mod_A_class をインポートすると、この名前 Mod_A_class がインポートされていないため、名前エラーが発生します。

python 循環インポート - 例 3

出力:

Traceback (most recent call last):
  File "c:\Users\Dell\Desktop\demo\main.py", line 1, in <module>
    from My_Module_A import My_FUNC_A
  File "c:\Users\Dell\Desktop\demo\My_Module_A.py", line 9, in <module>
    from MY_Module_B, import MY_Func_B1
  File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 22, in <module>
    class Mod_b_class:
  File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 23, in Mod_b_class
    def __init__(self,a : Mod_A_class):
NameError: name 'Mod_A_class' is not defined

これを修正するには、__future__ import annotations からこれを追加します。 これは何をしますか?

注釈の動作方法が変わります。 Mod_b_class を名前として評価する代わりに、すべての注釈が文字列に変換されます。

python 循環インポート - 例 4

Mod_b_class という名前がインポートされていなくても、文字列 Mod_b_class は確実に存在するため、ここでは実行時にエラーは発生しません。 ただし、__future__ インポートからこれらのいずれかを実行するたびに、その動作が永久に保証されるとは限らないことに注意してください。

現在、これにより注釈が文字列として処理されますが、それらを単に遅延評価するようにするという競合する提案があります。 したがって、実行時にそれらを調べて型ヒントを検査しない限り、それらは評価されません。

これはおそらくコード ベースに影響を与えることはありませんが、それでも注意する必要があります。 別の解決策は、from module imports の代わりに import modules を再度使用することですが、それでも __future__annotations が必要になります。

python 循環インポート - 例 5

モジュール My_Module_A.py の完全なソース コード。

# from __future__ import annotations

# from typing import TYPE_CHECKING

# from MY_Module_B import MY_Func_B1

# if TYPE_CHECKING:
#     from MY_Module_B import Mod_b_class

# def My_FUNC_A():
#     b : Mod_b_class = MY_Func_B1()


# class Mod_A_class:
#     def __init__(self,b : Mod_b_class):
#         self.b=b


from __future__ import annotations
import MY_Module_B


def My_FUNC_A():
    b: MY_Module_B.Mod_b_class = MY_Module_B.MY_Func_B1()


class Mod_A_class:
    def __init__(self, b: MY_Module_B.Mod_b_class):
        self.b = b

モジュール MY_Module_B.py の完全なソース コード。

from __future__ import annotations

import My_Module_A


def MY_Func_B1():
    ...


class Mod_b_class:
    def __init__(self, a: My_Module_A.Mod_A_class):
        self.a = a

main.py ファイルの完全なソース コード。

from My_Module_A import My_FUNC_A


def My_main_Func():
    My_FUNC_A()


if __name__ == "main":
    My_main_Func()
著者: Salman Mehmood
Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

関連記事 - Python Import