pythonでは、デフォルトでは実行フォルダおよびその配下のフォルダだけがモジュール検索対象のパスとなっています。
そのため、ある程度プロジェクトが大きくなってきて、共通モジュールを特定のフォルダに整理してまとめたりすると、パスが通らず困ったりします。
今回は、兄弟階層の共通モジュールのimport方法を説明します(もちろん親階層でも同じ方法で対応できます)。
はじめに
フォルダ構成
今回は以下のようなフォルダ構成でmain.py
にlogger.py
をimportする方法について解説します。
┣ module
┃ ┗ logger.py
┗ business
┗ main.py
logger.pyの実装
import logging
formatter = '%(asctime)s [%(levelname)s] %(message)s'
logging.basicConfig(level=logging.DEBUG, format=formatter)
def info(message: str, *args, **kwargs):
logging.info(message, *args, **kwargs)
main.pyの実装
from module import logger
def main():
logger.info('module imported successfully.')
if __name__ == "__main__":
main()
問題点
module
フォルダがモジュール検索パスに入ってないので、ModuleNotFoundError
が発生します
$ python main.py
Traceback (most recent call last):
File "main.py", line 4, in <module>
from module import logger
ModuleNotFoundError: No module named 'module'
①シンボリックリンクを利用
以下のように実行ファイルが配置されているbusiness
フォルダに、module
フォルダへのシンボリックリンクを作成します。
$ cd business
$ ln -s ../module module
$ ls -la
-rw-r--r-- 1 hoge staff 147 11 29 09:44 main.py
lrwxr-xr-x 1 hoge staff 9 11 29 10:00 module -> ../module
この状態だと、問題なく実行できます。
$ python main.py
2019-11-29 10:02:19,624 [INFO] module imported successfully..
この方法の特徴は
- ○ gitにcommitできて、linux環境にgit pullして問題なく動作する
- × 実行フォルダ毎にシンボリックリンクを作成する必要がある
- × おそらくwindowsで動かない
のような感じで、正直微妙な感じですね。
②環境変数 PYTHONPATH を利用
環境変数PYTHONPATH
にモジュール検索パスを追加する方法があります。
指定するパスは、実行フォルダからの相対パス、もしくは、絶対パスとなります。
今回はfrom module import logger
という形でimportしているので、module
フォルダの親階層python_sample
をパスに追加します(以後の対応案は全て同様です)。
# 相対パス
export PYTHONPATH="..:$PYTHONPATH"`
# 絶対パス
export PYTHONPATH="/hoge/python_sample/:$PYTHONPATH"
これも問題なく実行できました。
$ python main.py
2019-11-29 10:26:37,813 [INFO] module imported successfully.
この方法の特徴は
- ○ 環境変数一つだけ設定すればよい
- × 環境変数管理をしなければいけない
- 環境変数管理方法については別記事を書いてますので参照ください。
- pythonにおけるローカル環境での環境変数の設定
のような感じで、環境変数管理が面倒ですが、最有力候補ではないでしょうか。
③パス設定ファイル name.pth にパスを設定
詳細はこちらを参照いただきたいですが、lib/pythonX.Y/site-packages
配下にパス設定ファイルname.pth
を配置して、そこにモジュール検索パスを記載する方法があります。
site-packages
フォルダは、pythonのインストールフォルダの配下にありますので、そこに今回はpython_sample.pth
ファイルを作成しました。
cd /fuga/lib/python3.7/site-packages
echo "/hoge/python_sample" > python_sample.pth
このファイルを作成した状態であれば、問題なく実行できました。
$ python main.py
2019-11-29 10:57:09,094 [INFO] module imported successfully.
この方法の特徴は
- ○ パス設定ファイルだけ管理すればよい
- × パス設定ファイルがプロジェクト外なので、git管理もできず、ファイルの管理が難しい
のような感じだで、パス設定ファイルの管理が難しく、あまりおすすめできないです。
④実行時にモジュール検索パスリスト sys.path に追加
sys.path
はモジュール検索パスの文字列の単純なリスト(List[str]
)です。
実行時に、sys.path
に検索対象の文字列を追加します。main.py
に以下のような感じで実装します。
import sys
import os
sys.path.append(os.pardir) # 親ディレクトリの取得はPathを使う手もある
from module import logger
def main():
logger.info('module imported successfully.')
if __name__ == "__main__":
main()
from module import logger
より前にsys.path
に対象パスを追加する必要があるので、地味に面倒です。autopep8を入れていると、importがトップレベルに自動で移動され
from module import logger
import sys
import os
sys.path.append(os.pardir)
のようになり、from module import logger
する時点でsys.path
に対象パスが追加されておらずうまく動きません。
なので、autopep8の該当動作をOFFにする必要があります。VSコードの場合だと、.vscode/settings.json
に以下の設定を追加します。
"python.formatting.autopep8Args": [
"--ignore=E402",
],
さらに、flake8を入れている場合、そちらでmodule level import not at top of fileflake8(E402)
というワーニングが出るので、そちらのチェックもOFFにする必要があります。.vscode/settings.json
に以下の設定を追加します。
"python.linting.flake8Args": [
"--ignore=E402",
],
この方法の特徴は
- ○ 全てソースコード上で管理できる。当然git管理もできる
- × 全部の実行ファイルに実装が必要
- × 遠い場所にあるフォルダだったりすると、パスを追加する実装がより煩雑
- × pep8のE402に準拠できなくなる
のような感じで個人的には、ソースコード上で全てが分かるのはポイント高いので、これもありだと思っています。
⑤ module 配下をパッケージにしてpipインストール
プロジェクト直下に、setup.py
とsetup.cfg
の二つのファイルを準備して、パッケージを作成します。
まず、以下のようにsetup.py
を作成します。
from setuptools import setup
setup()
次に、以下のようにsetup.cfg
を作成します。module
フォルダ配下をmodule
というパッケージにしています。
[metadata]
name=module
version=0.1.0
[options]
packages = module
その上で、pipコマンドを実行するとパッケージが作成され、site-packagesにインストールされます。
$ pip install -U .
Processing /hoge/python_sample
Installing collected packages: module
Running setup.py install for module ... done
Successfully installed module-0.1.0
この状態であれば、問題なく実行できました。
$ python main.py
2019-11-29 12:47:15,190 [INFO] module imported successfully.
この方法の特徴は
- ○ 全てソースコード上で管理できる。当然git管理もできる
- × 共通モジュールに変更が入った際、
pip install
し直す必要がある- 開発時、他の人が共通モジュールに手を入れて、
git pull
したけど、pip install
し忘れて古いバージョンで実装、みたいなことが発生する可能性大
- 開発時、他の人が共通モジュールに手を入れて、
のような感じで、とても正統派な感じなのですが、古いバージョンで実装してしまうのが怖いので、個人的にはやめておこうと思いました。
さいごに
どれも一長一短ですが、個人的には環境変数の管理をプロジェクト内でちゃんとやる前提で
②環境変数PYTHONPATH
を利用
が一番しっくりくると感じました。