【Python】複数の.pyに分けたらimportエラー:小さなパッケージに整理する

PythonのTopに戻る


複数の.pyへ分けたときに相対importが失敗するなら、パッケージの外側からpython -m パッケージ名で起動する形へ整理する。パッケージ内部のファイルを直接実行すると、親パッケージの情報が足りず、相対importが使えないことがある。検索経路を毎回書き換えるより、処理の配置と実行入口をそろえる方が扱いやすい。

パッケージの外側を作業の基準にする

ここではprojectフォルダーの中にlabtoolフォルダーを作り、その中に__init__.py、stats.py、__main__.pyを置く。__init__.pyは空でよい。通常の小さなパッケージとして扱うことを明示するために置いている。namespace packageという別の仕組みもあるが、この例では使わない。

stats.pyは計算だけを担当し、__main__.pyが単体実行の入口になる。起動する場所はlabtoolの中ではなく、その一つ外側のprojectである。Pythonからlabtoolという名前を見つけられる配置になっているかを、まず確認する。

計算処理と入口を書く

labtool/stats.py

def centered(values):
    if not values:
        raise ValueError("values must not be empty")
    mean = sum(values) / len(values)
    return [value - mean for value in values]

labtool/__main__.py

from .stats import centered


def main():
    print(centered([1, 2, 3]))


if __name__ == "__main__":
    main()

from .statsの先頭のドットは、同じパッケージ内のstatsを指定している。作業ディレクトリにある偶然同名のファイルを探すという意味ではない。この指定を解釈するためには、そのファイルがどのパッケージに属するかという実行時の情報が必要になる。

ファイルのパスではなくモジュールとして起動する

projectフォルダーで実行するコマンド

python -m labtool

実行結果

[-1.0, 0.0, 1.0]
Direct file execution: ImportError

python -m labtoolはパッケージを探し、その__main__.pyを入口として実行する。比較のために内部ファイルを直接起動すると、この例ではImportErrorになることも確認した。失敗側の出力は例外クラスだけを抜き出したもので、長いパスを含むトレースバック全体は省略している。

同じ構成でも、python labtool/__main__.pyという実行方法へ戻すと相対importの文脈を失う。エディターの実行設定も、単に開いているファイルを走らせる形か、モジュールとして実行する形かを区別して設定する必要がある。

他のコードからも計算関数を使う

この配置なら、project内の別のコードからfrom labtool.stats import centeredと書いて関数を利用できる。__main__.pyを経由して関数を取り出す必要はない。入口と部品を分けておけば、単体実行用のデータを準備せずに計算だけを確認できる。

__init__.pyには、使いやすい公開名をまとめることもできる。ただし、最初から多くのモジュールを読み込むと、import時の処理や循環依存が分かりにくくなる。小さな構成では空のまま始め、利用側が必要なモジュールを明示してimportする方が追跡しやすい。

sys.pathへの追加を増やす前に確認する

sys.path.appendで上位フォルダーを追加すれば、その場では動く場合もある。しかし、起動場所ごとに追加内容が変わると、別PCや自動実行では同じ名前の別ファイルを読み込むなど、再現しにくい不具合になる。まずパッケージの名前と起動場所を一定にしよう。

どの場所からでも自作ライブラリを使いたいなら、次の段階としてpyproject.tomlを用意し、開発用のeditable installや通常インストールを行う。その場合も、パッケージ内の相対importと実行入口を整理した構成はそのまま役立つ。

なお、パッケージ化だけで循環importが解消されるわけではない。statsから入口を読み、入口からstatsを読むといった双方向の依存は避ける。計算をする部品は入口を知らず、入口が部品を組み合わせる向きにそろえると、変更箇所も分かりやすくなる。

実際の解析へ広げる前に、モジュール起動、関数の直接import、空入力などの境界条件をそれぞれ確認する。起動方法が正しくても計算処理の誤りは別に起こるため、配置の検証と結果の検証を分けることが大切である。

動作確認と参考資料

掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。

関連するTips


PythonのTopに戻る