モジュール間の循環参照(circular import)のエラーについて
はじめに
先週のPythonETLの作成で、以下のようなエラーが発生した。
ImportError: cannot import name 'MyClass' from partially initialized module 'module_a' (most likely due to a circular import)
この記事では、このエラーを再現させて、モジュール間のやり取りについての理解を深めていきたい。
サンプルの作成
モジュール間のやり取りに関するエラーを発生させたいので、以下のサンプルファイルを作成した。

main.pyは実行ファイル。module_a.pyからMyClassを読み込んで、そのインスタンス生成を行っている。
from module_a import MyClass
if __name__ == "__main__":
obj = MyClass()
module_a.pyはMyClassを定義しているファイル。module_b.pyからsome_function関数を読み込んで、実行している。
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
module_b.pyはsome_function関数を定義しているファイル。module_a.pyからMyClassを読み込んで、インスタンス生成している。
from module_a import MyClass
def some_function():
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
循環参照エラーを発生させた
main.pyを実行すると、循環参照のエラーが発生した。

(venv) Mac:python shibatahiroshitaka$ /Users/shibatahiroshitaka/Downloads/python/venv/bin/python /Users/shibatahiroshitaka/Downloads/python/between_module/main.py
Traceback (most recent call last):
File "/Users/shibatahiroshitaka/Downloads/python/between_module/main.py", line 1, in <module>
from module_a import MyClass
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 1, in <module>
from module_b import some_function
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 1, in <module>
from module_a import MyClass
ImportError: cannot import name 'MyClass' from partially initialized module 'module_a' (most likely due to a circular import) (/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py)
処理の流れを追う
エラースタックトレースを見て、エラー発生までの処理の流れを追っていこう。
まずは、main.pyでmodule_aからMyClassが読み込まれる。
Traceback (most recent call last):
File "/Users/shibatahiroshitaka/Downloads/python/between_module/main.py", line 1, in <module>
from module_a import MyClass
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 1, in <module>
from module_b import some_function
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 1, in <module>
from module_a import MyClass
from module_a import MyClass
if __name__ == "__main__":
obj = MyClass()
次に、module_a.pyでmodule_bからsome_functionが読み込まれる。
Traceback (most recent call last):
File "/Users/shibatahiroshitaka/Downloads/python/between_module/main.py", line 1, in <module>
from module_a import MyClass
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 1, in <module>
from module_b import some_function
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 1, in <module>
from module_a import MyClass
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
最後に、module_b.pyでmodule_aからMyClassが読み込まれる。
Traceback (most recent call last):
File "/Users/shibatahiroshitaka/Downloads/python/between_module/main.py", line 1, in <module>
from module_a import MyClass
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 1, in <module>
from module_b import some_function
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 1, in <module>
from module_a import MyClass
from module_a import MyClass
def some_function():
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
循環参照のイメージ
module_aとmodule_bの間で循環参照が起こっている。

module_aのMyClassはこちら。初期化時にsome_function関数を呼び出している。
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
module_bのsome_function関数はこちら。MyClassのインスタンスを生成している。
from module_a import MyClass
def some_function():
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
エラーメッセージはこちら。部分的に初期化されたmodule_aからMyClassを読み込めません。(循環参照のためと思われます)
ImportError: cannot import name 'MyClass' from partially initialized module 'module_a' (most likely due to a circular import)
部分的に初期化されたmodule_aとは
mainでmodule_aを読み込み、module_aでmodule_bを読み込んでいる。そのため、module_aでは、まだMyClassの初期化が行われていない。
main.py
from module_a import MyClass
if __name__ == "__main__":
obj = MyClass()
module_a.py
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
Myclassの初期化が行われていない状態で、module_aからMyclassを読み込もうとしているので、先ほどのエラーが発生した。
module_b.py
from module_a import MyClass
def some_function():
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
先ほどのエラー
ImportError: cannot import name 'MyClass' from partially initialized module 'module_a' (most likely due to a circular import)
エラーの回避策
遅延読み込みを行うとエラーが回避できる場合がある。
module_b.pyのsome_function関数の中で、MyClassの読み込みを行った。
def some_function():
from module_a import MyClass
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
そうすると以下のエラーが発生した。
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 4, in some_function
obj = MyClass() # ここで循環参照が発生する
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 6, in __init__
some_function()
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 4, in some_function
obj = MyClass() # ここで循環参照が発生する
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_a.py", line 6, in __init__
some_function()
File "/Users/shibatahiroshitaka/Downloads/python/between_module/module_b.py", line 3, in some_function
print("some_function from module_b")
RecursionError: maximum recursion depth exceeded while calling a Python object
module_bで行っているインスタンス生成と、module_aで行っているsome_function関数の実行が循環している。
module_b
def some_function():
from module_a import MyClass
print("some_function from module_b")
obj = MyClass() # ここで循環参照が発生する
module_a
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
RecursionError: maximum recursion depth exceeded while calling a Python objectを解決する
module_bでMyClassのインスタンス化を行わなければ、このエラーが解決した。
module_b
def some_function():
# from module_a import MyClass
print("some_function from module_b")
# obj = MyClass() # ここで循環参照が発生する
main.pyを実行
from module_a import MyClass
if __name__ == "__main__":
obj = MyClass()
module_a
from module_b import some_function
class MyClass:
def __init__(self):
print("MyClass from module_a")
some_function()
実行結果
(venv) Mac:python shibatahiroshitaka$ /Users/shibatahiroshitaka/Downloads/python/venv/bin/python /Users/shibatahiroshitaka/Downloads/python/between_module/main.py
MyClass from module_a
some_function from module_b
コメントを残す