attempted relative import beyond top-level package エラーに対処する

色々な所で記事化されているテーマではあるが、いくつかわかりにくかった部分があるので整理する。

どういうタイミングで発生するのか

書かれている通りではあるが、整理する。

  • attempt relative import

    • その名の通り、相対パスでのインポートを行う際に発生する

  • beyond top-level package

    • 「パッケージのトップレベル」を超えてはダメ

この、「パッケージのトップレベルを超える」とはどういうことか、というのが少しわかりにくい。これは、「パッケージのトップレベルをまたいではダメ」である。「パッケージのトップレベルより上に行ってはダメ」ではない。つまり、

\
 ├ top_module.py
 ├ second/
 │  ├ third/
 │  │  └ third_module.py
 │  └ second_module.py
 └ second-2/
    └ second-2_module.py

という構造の時に、 third_module.py からインポートしようとすると

from .. import second_module              # -> OK
from ... import top_module                # -> NG
from ...second-2 import second-2_module   # -> NG
from .... import some_module              # -> もちろんNG

というわけである。

モジュール開発の時にモジュール内でimportする時の注意点

当たり前なのだが、独自モジュールを開発する際に、外の名前空間を汚してはいけない。つまり、

\
 ├ package/
 │  ├ lib/
 │  │  └ module.py
 │  └ conf/
 │     └ config.py
 └ conf/
    └ config.py

のような構造の時に、 lib/module.py 内で package ディレクトリをルートと想定して

from conf import config

とかの絶対パス指定をやってしまうと、読み込む場所や拡張されたPYTHONPATHの環境状態次第で異なるもの(この場合だとルート直下の conf/config.py)を読み込んでしまうことになる。
これを解決するためには、モジュール内で相対パスとして読み込む必要がある。つまり、

from ..conf import config

となる。

IDEで開発する時(Visual Studio Codeの例)

ところで、「ルートディレクトリ」がどこになるかは実行環境により異なる。コマンド実行した場合はコマンドを実行した場所であることはわかりやすい。では、IDEで開発をする時に、IDEの機能でパス解決している場合はどうなるか。
基本的には、プロジェクトを開いているルートディレクトリ(ワークスペース)がルートとして判断される。そのため、開く場所によってパスが解決できたりできなかったりする。プロジェクト作成時に、どのディレクトリをルートとして開発するのかは決めてパス解決することが望ましい。また、VSCode の場合はプロジェクトのルートディレクトリにある .env ファイルを読み込んで環境変数拡張などを行うことができる。これを利用して PYTHONPATH を拡張することで読み込むこともできる。ただし、これを行う時はモジュール内の名前空間が統一されないので、思わぬコンフリクトを発生させることがあることを注意して実施する必要がある。例えば、上記例だと、 PYTHONPATH="package:$PYTHONPATH" で拡張すると、 conf モジュールがコンフリクトするので、対象によってはインポートに失敗する。

また、上述の通り、VSCodeではワークスペースがルートと判断されるので、上記例の package フォルダをワークスペースとして開くと、 lib/module.py における

from ..conf import config

は attempted relative import beyond top-level package エラーが発生する。そのため、パッケージ開発時にはパッケージルートのディレクトリに対して1つ以上上のディレクトリをワークスペースとするようにリポジトリを設計するべきである。

この記事が気に入ったらサポートをしてみませんか?