LoginSignup
17
19

More than 3 years have passed since last update.

循環import問題で見える、__name__ == "__main__"の必要性

Posted at

循環import問題というものがあります。
例えば、

hoge.py
# coding: utf-8

import piyo

class Base:
    pass

class Hoge(Base):
    pass

と、

piyo.py
# coding: utf-8

import hoge

class Piyo(hoge.Base):
    pass

という2つのPythonスクリプトがあったとして、「python3 hoge.py」と実行するとエラーは起きないのですが、「python3 piyo.py」と実行すると、

Traceback (most recent call last):
  File "piyo.py", line 3, in <module>
    import hoge
  File "hoge.py", line 3, in <module>
    import piyo
  File "piyo.py", line 5, in <module>
    class Piyo(hoge.Base):
AttributeError: partially initialized module 'hoge' has no attribute 'Base' (most likely due to a circular import)

というようにAttributeErrorが発生してしまいます。
(今見ると、ご丁寧に「most likely due to a circular import」と言ってくれてるんですね)
これに対して、Baseクラスは別のスクリプトにし、hoge.py/piyo.pyの双方からそのスクリプトをimportする、というのが解決法の一つになります。

はてさて、時折このような問題が出るとQ&Aサイトで出てくるのですが、回答は先のようなものでいいのですが、実は例外が発生する原因は結構深い話で、いつも「んー?」と悩んで、理由がわかると「あ、これ、前にも悩んだやつですわ。またかい」と思っています。
今日は、その事をメモっておこうかと思います。


さて、この問題の原因を理解するために、知っておかなければならないPythonの仕様を挙げておきます。

  • import文は、そのモジュールのスクリプトを実行する。
  • モジュールAの中でモジュールBをimportされた場合、モジュールAのそれ以降の処理はモジュールBの処理が終わってから実行される。
  • importされたモジュールオブジェクトは、sys.modulesにモジュール名をキーとして保存され、2回目以降にimportされた場合は、sys.modulesに保存されたオブジェクトを返すだけで、モジュールのスクリプトの処理は実行されることはない。

さて、これを踏まえて、先の「python3 piyo.py」を実行した際に何が起きるか、検証してみましょう。

  1. piyo.py」の中で、「import hoge」とhoge.pyがimportされます。この時点で、Piyoクラスはまだ定義されていません。
  2. importされた「hoge.py」の中で、「import piyo」が実行されますが、「piyo.py」は既にロードされているので、何もしません。
  3. hoge.py」のそれ以降に書かれている、Baseクラス、Hogeクラスが定義されます。

このように、何の問題もなく実行され…あれ?
なんか違います。

今度は、「python3 hoge.py」を実行してみましょう。

  1. hoge.py」の中で、「import piyo」とpiyo.pyがimportされます。この時点でBaseクラスとHogeクラスは定義されていません。
  2. importされた「piyo.py」の中で、「import hoge」が実行されますが、「hoge.py」は既にロードされているので、何もしません。
  3. Piyoクラスの派生元クラスとして、hoge.Baseクラスを参照しますが、hoge.pyの中でBaseクラスの定義が実行されていないので、hogeモジュールの属性にBaseというものはなく、AttributeError例外が発生します。

このように、エラーが発生して…あれ?

このように、原因を突き止めると実際の動作と逆のようになります。
いつもここで一旦悩むのですが、もう一つPythonの仕様を思い出して解決します。

  • 最初に起動するスクリプトは、「__main__」というモジュール名になる。

これを踏まえて、もう一度「python3 piyo.py」の動作を検証してみましょう。

  1. piyo.py」の中で、「import hoge」とhoge.pyがimportされます。この時点で、Piyoクラスはまだ定義されていません。
  2. importされた「hoge.py」の中で、「import piyo」が実行されます。「piyo.py」は既にロードされていますがそれはモジュール名が「__main__」というものであり、「piyo」というモジュールはロードされていないため、再度「piyo.py」が実行されます。この時点で、この後に書かれているBaseクラスとHogeクラスは実行されていないので、まだ定義されていません。
  3. importされた「piyo.py」の中で、「import hoge」が実行されますが、「hoge.py」は既にロードされているので、何もしません。
  4. Piyoクラスの派生元クラスとして、hoge.Baseクラスを参照しますが、hoge.pyの中でBaseクラスの定義が実行されていないので、hogeモジュールの属性にBaseというものはなく、AttributeError例外が発生します。

確かに、AttributeError例外が発生しますね。
試しに、

piyo.py
# coding: utf-8

import hoge
import sys
print('\n'.join([repr(n) for n in sys.modules.items()]))

class Piyo(hoge.Base):
    pass

と、sys.modulesの中身を見てみましょう。

('__main__', <module '__main__' from 'a/piyo.py'>)
('piyo', <module 'piyo' from '/path/to/a/piyo.py'>)

(必要なものを除き、省略)
上記のように、「piyo.py」が二度読み込まれているのが解ると思います。


さて、おさらいです。

基本的に、スクリプトは一回importされたら二回実行される事はないのですが、起動時に実行するスクリプトは二回実行される事があるという事です。
ですので、起動時に実行されるスクリプトは、

if __name__ == '__main__':
    main()

というように、自身のモジュール名を見て、最初の一回のみ実行するようにすべき、ということですね。

17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19