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)
最も可能性の高い原因は循環インポートであることがわかります。 この種のインポート サイクルが実行時に発生する可能性がある最も単純で最も一般的な方法は次のとおりです。
そのため、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_A
とMY_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
モジュールのインポートが終了します。
出力:
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
がインポートされていないため、名前エラーが発生します。
出力:
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
を名前として評価する代わりに、すべての注釈が文字列に変換されます。
Mod_b_class
という名前がインポートされていなくても、文字列 Mod_b_class
は確実に存在するため、ここでは実行時にエラーは発生しません。 ただし、__future__
インポートからこれらのいずれかを実行するたびに、その動作が永久に保証されるとは限らないことに注意してください。
現在、これにより注釈が文字列として処理されますが、それらを単に遅延評価するようにするという競合する提案があります。 したがって、実行時にそれらを調べて型ヒントを検査しない限り、それらは評価されません。
これはおそらくコード ベースに影響を与えることはありませんが、それでも注意する必要があります。 別の解決策は、from module imports
の代わりに import modules
を再度使用することですが、それでも __future__
の annotations
が必要になります。
モジュール 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()
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