モジュール間の循環参照(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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です