Flaskのバックエンドを書いている途中にimport
のエラーで不思議に思った点があるので検証を行いました。
問題のコード
以下のようなディレクトリ構造で、それぞれのファイルには次のような内容が書かれています。
意図した動作としては、main.py
を実行するとmain.py
からpkg1
パッケージ内のmod1
モジュールの関数func2
をロードしてmain.py
内で実行します。
mod1
モジュールの関数func2
はmain.py
にある関数func1
の返り値をprint
する関数です。
mytest
|-- pkg1/ (パッケージ1)
|-- __init__.py
|-- mod1.py (モジュール1)
|-- main.py (実行ファイル)
# main.py
from pkg1.mod1 import func2
var1 = 'foo'
def func1():
return var1
if __name__ == '__main__':
func2()
# pkg1/__init__.py
# pkg1/mod1.py
from main import func1
def func2():
print(func1())
インポート時にエラーが起きる
main.py
とpkg1/mod1.py
で相互にインポートしているので、実行時に次のようなエラーが発生します。
# (bash)
$ python main.py
Traceback (most recent call last):
File "main.py", line 2, in <module>
from pkg1.mod1 import func2
File "...\mytest\pkg1\mod1.py", line 2, in <module>
from main import func1
File "...\mytest\main.py", line 2, in <module>
from pkg1.mod1 import func2
ImportError: cannot import name 'func2' from 'pkg1.mod1' (...\mytest\pkg1\mod1.py)
解決策
main.py
のみを編集する解決策としては次の2通りが有ります。
解決策1 : ローカルスコープインポート
1つ目の解決策は、循環インポートを応急的に解消する方法です。
# main.py
var1 = 'foo'
def func1():
return var1
if __name__ == '__main__':
from pkg1.mod1 import func2
func2()
main.py
でのモジュールの参照を、main.py
がトップレベルとして実行された時に限定します。
これで、mod1.py
でmain.py
を参照した時にはmod1.py
が参照されないため、循環インポートが解消されて正常に実行出来るようになります。
解決策2 : ワイルドカードインポート
2つ目の解決策は、func2
を名指しでインポートしていた所をワイルドカードインポートに変更します。
# main.py
from pkg1.mod1 import *
var1 = 'foo'
def func1():
return var1
if __name__ == '__main__':
func2()
こうすると、プログラムが正常に実行出来るようになります。
ちょっと待って
1つ目の解決策は循環インポートが解消されたので実行出来るようになるのは理解出来ます。
しかし、2つ目の解決策は何も解決しているように見えません。
検証
どういう経路で実行されているのか知るために、main.py
とpkg1/mod1.py
のファイルの先頭とインポートの前後にprint()
を置きました。
# ファイルの先頭
print('Loading ファイル名 =', __name__)
# インポートの前後
print('Before import, pkg1/mod1.py =', __name__)
# from ... import ...
print('After import, pkg1/mod1.py =', __name__)
また、var1
が定義された時間を調べるためにtime
モジュールを使います。
# var1 = 'foo'
var1 = time.time()
print(__name__, var1)
解決策1の経路
解決策1の場合の経路です。
# (bash)
$ python main.py
Loading main.py = __main__ # 実行コマンドによるロード
__main__ 1580243970.7842584 # __main__でvar1が定義される
Before import, main.py = __main__ # main => pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1 # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 => main 開始
Loading main.py = main # pkg1/mod1からののimportによるロード
main 1580243970.7892146 # mainでvar1が定義される
After import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 => main 終了
After import, main.py = __main__ # main => pkg1/mod1 終了
1580243970.7892146 # mainでのvar1の結果が呼ばれている
ローカルスコープインポートのため、main.py
からpkg1/mod1.py
へのインポートは1回しか発生していません。
また、実行コマンドで一度main.py
は実行されていますが、pkg1/mod1.py
からのインポート時にもう一度実行してその結果をインポートしているのが、最後の結果から分かります。
解決策2の経路
解決策2の場合の経路です。
# (bash)
$ python main.py
Loading main.py = __main__ # 実行コマンドによるロード
Before import, main.py = __main__ # main => pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1 # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 => main 開始
Loading main.py = main # pkg1/mod1からのimportによるロード
Before import, main.py = main # main => pkg1/mod1 開始
# pkg1/mod1.pyが読み込まれたらLoading ...が表示されるはず
After import, main.py = main # main => pkg1/mod1 終了
main 1580244277.4291725 # mainでvar1が定義される
After import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 => main 終了
After import, main.py = __main__ # main => pkg1/mod1 終了
__main__ 1580244277.4301698 # __main__でvar1が定義される
1580244277.4291725 # mainでのvar1の結果が呼ばれている
ローカルスコープインポートと違い、コードが実行される度にインポート自体は行われているようです。
但し、解決策2ではインポートガードが機能しているので、pkg1/mod1.py
の2回目の実行は阻止されています。
エラーの場合の経路
最初に示したコードの場合の経路です。
# (bash)
$ python main.py
Loading main.py = __main__ # 実行コマンドによるロード
Before import, main.py = __main__ # main => pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1 # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 => main 開始
Loading main.py = main # pkg1/mod1からのimportによるロード
Before import, main.py = main # main => pkg1/mod1 開始
Traceback (most recent call last): # インポートエラー発生
...
ImportError: cannot import name 'func2' from 'pkg1.mod1' (...\mytest\pkg1\mod1.py)
pkg1/mod1.py
の2回目の実行の前までは解決策2と同じ経路のように見えます。
考察
解決策1と解決策2では、インポートの位置による実行経路の違いが見られ、どちらもpkg1/mod1.py
のインポート時に実行された結果を利用するようでした。
しかし、解決策2の場合ではインポートガードによってpkg1/mod1.py
の2回目の実行が回避されているのに対し、エラーの場合ではそのタイミングでエラーが発生しています。
つまり、名指しでインポートを行うと、インポート名を明示的に指定している関係でインポートガードが行えないのではないかと考えられます。
まとめ
循環インポートはコードが無駄に実行されてしまったり今回のようなちょっとしたエラーにもなるのでそもそも循環インポートになるような構造は避けるべきですが、仮に循環インポートになってしまったとしてもPythonの賢いインポートガードによって上手にループが回避されているのが分かりました。
ワイルドカードインポートはPEP 8で非推奨になっているし名前空間が分かりづらくなるので、どうしても循環構造が生まれてしまう場合は解決策1の方が望ましいかも知れません。勿論、ファイルの途中でインポートするのもPEP 8では推奨されていないので、from pkg1 import mod1
でモジュールレベルインポートした後、関数オブジェクトのようにfunc2 = mod1.func2
で別名を付けるのが最適解だと思います。
ワイルドカードを使った import (from
import *) は避けるべきです。(pep8-ja/import)
ちなみに、このエラーはFlask-SQLAlchemyでビューの作成をblueprint
でしようとしていてこのような構造になったので発生しました。