Tech カテゴリのイメージ

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 package
SystemError: Parent module '' not loaded, cannot perform relative import

言っているのは、相対インポートを使いたいのに、そのファイルがどのパッケージに属しているか分からない、ということになる。

何が起きているのか

こういう構成があるとする。

myproject/
mypkg/
__init__.py
main.py
utils.py

main.pyの中で相対インポートを書く。

from .utils import helper

そして次のように実行すると落ちる。

Terminal window
python mypkg/main.py

理由はこうなる。Pythonがファイルを直接実行すると、そのモジュールの名前は__main__になる。__main__にはパッケージの情報が付かないので、.が指す親パッケージが分からない。相対インポートは、自分がどのパッケージの中にいるかを知っていることが前提なので、成立しない。

__init__.pyがあるかどうかは関係ない。ファイルを直接指定した時点で、パッケージの一員としては扱われなくなる。

直し方1. モジュールとして実行する(推奨)

-mを付けて、パッケージ名からドット区切りで指定する。

Terminal window
cd myproject
python -m mypkg.main

この形ならmain.pymypkg.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.py
run.py
from mypkg.main import main
main()
Terminal window
python run.py

run.pyはパッケージの外にあるので直接実行してよく、そこから読み込まれるmypkg.mainはパッケージの一員として扱われる。

直し方4. パッケージとしてインストールする

配布や本格的な開発なら、編集可能な形でインストールしてしまうのがいちばん確実になる。

Terminal window
pip install -e .

pyproject.tomlsetup.pyが要るが、これをやると、どのディレクトリから実行してもimport mypkgが通るようになる。テストを走らせるときの取り回しも楽になる。

やらないほうがいいこと

検索するとよく出てくるが、避けたほうがよい対処もある。

import sys, os
sys.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をその場で足すやり方は壊れやすいので常用しない

※本記事にはアフィリエイト広告を含みます。