ImportError: attempted relative import with no known parent package の原因と直し方[Python]
パッケージを作りはじめると必ず一度は踏むのがこれになる。
ImportError: attempted relative import with no known parent package古いバージョンでは別の文言になる。
ValueError: attempted relative import beyond top-level packageSystemError: Parent module '' not loaded, cannot perform relative import言っているのは、相対インポートを使いたいのに、そのファイルがどのパッケージに属しているか分からない、ということになる。
何が起きているのか
こういう構成があるとする。
myproject/ mypkg/ __init__.py main.py utils.pymain.pyの中で相対インポートを書く。
from .utils import helperそして次のように実行すると落ちる。
python mypkg/main.py理由はこうなる。Pythonがファイルを直接実行すると、そのモジュールの名前は__main__になる。__main__にはパッケージの情報が付かないので、.が指す親パッケージが分からない。相対インポートは、自分がどのパッケージの中にいるかを知っていることが前提なので、成立しない。
__init__.pyがあるかどうかは関係ない。ファイルを直接指定した時点で、パッケージの一員としては扱われなくなる。
直し方1. モジュールとして実行する(推奨)
-mを付けて、パッケージ名からドット区切りで指定する。
cd myprojectpython -m mypkg.mainこの形ならmain.pyはmypkg.mainという名前で読み込まれ、親パッケージがmypkgだと分かるので相対インポートが通る。
注意点として、実行するのはパッケージの1つ上の階層からになる。mypkgの中に入ってpython -m mainとしても解決しない。
拡張子は付けない。python -m mypkg.main.pyは誤りになる。
直し方2. 絶対インポートに変える
相対にこだわらないなら、パッケージ名から書く。
from mypkg.utils import helperこちらはファイルを直接実行しても、mypkgが見つかる場所から起動していれば通る。プロジェクトのルートで実行する前提なら、絶対インポートのほうが素直なことが多い。
パッケージが見つからない場合はModuleNotFoundErrorになる。その場合はパッケージの置き場所の問題なので、ModuleNotFoundErrorのほうを見てほしい。
直し方3. 実行用の入口を外に置く
パッケージの中身は相対インポートのまま保ち、実行する入口だけ外に置く構成もある。
myproject/ run.py mypkg/ __init__.py main.py utils.pyfrom mypkg.main import mainmain()python run.pyrun.pyはパッケージの外にあるので直接実行してよく、そこから読み込まれるmypkg.mainはパッケージの一員として扱われる。
直し方4. パッケージとしてインストールする
配布や本格的な開発なら、編集可能な形でインストールしてしまうのがいちばん確実になる。
pip install -e .pyproject.tomlかsetup.pyが要るが、これをやると、どのディレクトリから実行してもimport mypkgが通るようになる。テストを走らせるときの取り回しも楽になる。
やらないほうがいいこと
検索するとよく出てくるが、避けたほうがよい対処もある。
import sys, ossys.path.append(os.path.dirname(os.path.dirname(__file__)))これで動くことは動くが、実行するファイルごとに書く必要があり、階層を変えると壊れる。他人が読んだときに意図が分かりにくい。一時しのぎ以上には使わないほうがいい。
init.pyについて
Python 3.3以降は__init__.pyがなくてもパッケージとして扱われる仕組みがある。ただし、明示的に置いておいたほうが挙動が安定するので、通常のパッケージには置いておくことをすすめたい。
なお__init__.pyを置いても、ファイルを直接実行する限り今回のエラーは解決しない。ここは切り分けておきたい。
📘 パッケージの仕組みを腰を据えて理解するなら、独習Python。
まとめ
・原因は、ファイルを直接実行するとモジュール名が__main__になり、親パッケージが分からなくなること
・__init__.pyの有無とは関係がない
・基本の対処はパッケージの1つ上の階層からpython -m mypkg.mainで実行する
・絶対インポートに書き換える方法も素直。プロジェクトルートから実行する前提になる
・実行用の入口をパッケージの外に置く構成も使いやすい
・配布や本格開発ならpip install -e .で入れてしまうのが確実
・sys.pathをその場で足すやり方は壊れやすいので常用しない
※本記事にはアフィリエイト広告を含みます。