Li::Feel

りとるす的雑記帳。

Python2.7: sys.pathで上位ディレクトリのモジュールを読み込む (Pythonスクリプト側の設定 + pylinterの設定)

モチベーション

次のようなディレクトリ構造で、group1/g1_main.pyの中、すなわちサブディレクトリから、上位ディレクトリにあるconfig.pyやutils.pyの中の関数や変数を読みたいことがあります。*1

./proj_root
├── __init__.py
├── config.py
├── group1
│   ├── __init__.py
│   ├── g1_main.py
│   └── g1_sub.py
└── utils.py

通常は、エントリポイント(pythonコマンドで実行引数になるpythonスクリプト)が最上位ディレクトリにある場合は、 普通に from ..config import Config というような相対インポートを書けばよいのですが、 エントリポイントがサブディレクトリにある場合はこの方法が使えません。

解決方法

そこで、PEP8違反ではありますが、次のような形式でsys.pathに親ディレクトリ(./proj_root)を追加することでサブディレクトリをエントリポイントとするPythonスクリプトを作成することが出来ます。

import os  # noqa
import sys  # noqa
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # noqa

ただし、この方法でsys.pathをいじってもpylinterには反映されず、VSCode上でプログラムを書いていると警告が消えません。 そこで、pylinterの設定ファイルでカバーしてあげます。 pylinterの設定ファイルは、ざっくり次のような順番で読み込まれることがドキュメントで示されています。

  1. pythonコマンドを実行したパス
  2. init.pyがある上位ディレクトリ(もし、さらに上位のディレクトリにinit.pyがあるなら、そこも探索する)
  3. 環境変数PYLINTRC

Running Pylint — Pylint 2.2.1 documentation

この仕組みを用いて、プロジェクトのルートディレクトリに.pylintrcを追加し、次のようなinit-hook(初期化時に実行されるpythonスクリプト)を記述します。

この記事で示している.pylintrcはpylint.config.PYLINTRCを取得して、pylintrcが設置されているディレクトリのパスを取得し、取得したパスをsys.pathに追加します。 これによって、プロジェクトのルートディレクトリがpylint内でもsys.pathに登録された状態になるので、pylintによる間違った警告を抑制することができます。

./proj_root
├── __init__.py
├── config.py
├── .pylintrc
├── group1
│   ├── __init__.py
│   ├── g1_main.py
│   └── g1_sub.py
└── utils.py
[MASTER]
init-hook="import os, sys, pylint; sys.path.append(pylint.config.PYLINTRC)"

Reference

Running Pylint — Pylint 2.2.1 documentation

python - PyLint "Unable to import" error - how to set PYTHONPATH? - Stack Overflow

*1:複数の実験スクリプトで共有したいスクリプトがあるけど、実験の区分ごとにしっかりディレクトリ分けをしたいというニッチなニーズとかを対象としています