この記事で解決したい問題
今まで適当に配置していたpythonファイルたち、プロジェクトが大きくなってフォルダ整理することにしました。ここで発生したのが自作ライブラリをインポートできない問題です。pythonでは実行フォルダおよびその配下のフォルダだけがモジュール検索対象のパスとなっているので、自分より上位にあるファイルからインポートができません。
今まではsys.path.appendを使って上位にあるファイルからインポートしていましたが、以下のようなデメリットがあり、sys.path.append を使わずに解決する方法はないものかと悩んでいました。
sys.path.append の弱点
sys.path.appendを使って上位ファイルからインポートをしようとすると、ファイルの最初の数行が必ず次のようになります。
import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
from MyModule.MyClass import MyClass
これの何が嫌かと言うと、
- 上位ファイルからインポートしたい全部のファイルでsys.path.appendしなければならない
- autopep8などコードの自動整形ツールを使うとimportの文が勝手に上に移動されてしまう
という点です。きれいなコードを書きたいという理想にどうしても相反してしまいますよね。
そんな悩みを今回解決してくれるのが direnv です。direnv はディレクトリ毎に .envrc に環境変数を定義することで、.envrcがあるディレクトリがカレントディレクトリになった時だけ環境変数を有効にしてくれるツールです。
筆者の開発環境
OS:macOS Catalina 10.15.6
brew 2.5.2
bash : 3.2.57(1)-release (x86_64-apple-darwin19)
Python : 3.6.10
今回の説明で用いるフォルダ構造
grand_mother
┣ mother
┃ ┗ me.py
┗ aunt
┗ cousin.py
me.py
でcousin.py
をインポートして使うことを想定して説明します。
me.py
とcousin.py
はそれぞれ以下のようになっています。
class Cousin:
name = 'Reon'
age = 14
from aunt.cousin import Cousin
print('name :', Cousin.name)
print('age :', Cousin.age)
direnvをインストールする
Macの場合はbrewコマンド一発です。
$ brew install direnv
Macでない場合はgit cloneします。
$ cd /path/to/directory # インストールしたい場所
$ git clone https://github.com/direnv/direnv
$ cd direnv
$ sudo make install
bashにhookを追加します。
eval "$(direnv hook bash)"
~/.bashrcの再読み込みしてdirenvを動作するようにします。
$ source ~/.bashrc
direnvを使えるようにする
環境変数を設定するプロジェクトのトップ(今回であれば grand_mother/ )に移動して、以下のコマンドを実行します。
$ cd 〇〇/grand_mother
$ direnv edit .
すると grand_mother/.envrc が作成されるので PYTHONPATH を追加します。
export PYTHONPATH="〇〇/grand_mother:$PYTHONPATH"
これで grand_mother 内のファイルで from mother import
や from aunt import
などができるようになります。
PYTHONPATH とは
PYTHONPATHはpythonがモジュールを読み込む時にデフォルトのpythonモジュールに加えてモジュールを探しに行くディレクトリを追加するものです。以下のコマンドで今設定されているPYTHONPATHを確認できます。
# PYTHONPATHを確認
$ echo $PYTHONPATH
# PYTHONを含む環境変数を全て表示
$ printenv | grep PYTHON
また、pythonがモジュールを読み込む時に読み込むディレクトリを全て見たいのであればpythonのsys.pathで確認できます。
import sys
print(sys.path)
(必要な人のみ) .envに環境変数を書きたい場合
この記事を参考にして、grand_mother/.env
の方に環境変数を書く場合も説明します。自分もすでに他の環境変数を .env に書いていたので、この方式を採用しました。
.envrcはこれだけです。
dotenv
.envの方に以下の一文を追加しましょう。
PYTHONPATH=/〇〇/grand_mother/
direnvの動作を確認する
.envrc を設定したら
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
こんなエラー文が表示されているかもしれません。その時はエラー文にある通り、direnv allow
を実行して、PYTHONPATHが追加されたことを確認します。.envrcを書き換えるたびにこの文章は表示されます。
$ direnv allow
direnv: loading ~/〇〇/grand_mother/.envrc
direnv: export +PYTHONPATH
.envrcがあるディレクトリから抜けると、direnvが一時無効化されます。
$ cd ..
direnv: unloading
再度、.envrcがあるディレクトリに入ると.envrcの内容が環境変数に追加されていることがわかります。
$ cd grand_mother/
direnv: loading .envrc
解決しました
これで me.py を実行するとうまくいきます。
$ python mother/me.py
name : Reon
age : 14
.envrcの運用
GitHubなどでソースコードを共有する場合、 .env や .envrc には盗まれると困る情報が乗っている。あるいは実行環境によって異なる値を設定すべき場合があるので、.gitignoreに.envrcを追加するなどして、リポジトリにコミットしないようにしましょう。
(補足) Amazon Linux にもdirenvを導入する
今回自分のローカルに合わせて、運用していたAmazon Linuxの方にもdirenvを入れる必要があったので、そのときの作業内容も書いておきます。
Amazon Linuxの環境
$ cat /etc/os-release
Amazon Linux AMI 2018.03
$ bash --version
GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)
まず、Amazon Linux にはdirenvを動かすためのgoが入ってないのでインストールする必要があります。
$ sudo yum install golang -y
そして、この記事の上の方にあるように、git cloneしてdirenvをインストールします。
$ cd /path/to/directory # インストールしたい場所
$ git clone https://github.com/direnv/direnv
$ cd direnv
$ sudo make install
bashにGOPATHとdirenvのhookを追加します。GOPATHは、ビルドをするための場所の指定なので、任意の場所で大丈夫です。
export GOPATH=$HOME/go
eval "$(direnv hook bash)"
~/.bashrcの再読み込みして、設定結果を確認しておきましょう。
$ source ~/.bashrc
$ echo $GOPATH
/home/ec2-user/go
あとは、記事の上の方に書いた direnvを使えるようにする のやり方で Amazon Linux でもdirenvが使えるようになりました。