asdf, direnv で pyenv-virtualenv っぽい Python のローカル実行環境(macOS)を構築する
偉い人は言いました。
Python の開発環境は永遠に終わらない。なぜなら人類の夢だからだ。 アシスタ=レザー=ハーン(1975ー ) https://shindanmaker.com/364165
閑話休題。
asdf, direnv のインストール
前提として、私は macOS のパッケージマネージャに Homebrew を、シェルに zsh を、zsh のプラグインマネージャに zhinit を使用しています。
まずは、asdf のインストール。
asdf は依存関係が簡単ではなさそうだったので、brew でインストールしちゃいます。
brew install asdf
その後、.zshrc
に以下を記述。
# zinit の設定の後に zinit ice lucid as'program' src'asdf.sh' zinit load asdf-vm/asdf
次に direnv に関して .zshrc
に記述します。
# direnv は zinit 公式の設定ですが、記述量がなかなか長い... zinit ice lucid from'gh-r' as'program' mv'direnv* -> direnv' \ atclone'./direnv hook zsh > zhook.zsh' atpull'%atclone' \ pick'direnv' src='zhook.zsh' zinit load direnv/direnv export DIRENV_LOG_FORMAT= # 標準出力を抑える export DIRENV_WARN_TIMEOUT=1h # venv 作成に時間が掛かると出てくる警告を抑える
ここまで書いて、zsh を再起動すると、direnv がインストールされ、asdf, direnv 共にパスが通っているはずです。
Python をインストールする
では、実際に pyenv-virtualenv っぽくしていきます。
asdf plugin add python python_latest=$(asdf latest python) asdf global python $python_latest system # 2 つスペース区切りで指定すると、fallback してくれるらしい asdf reshim python # 一応
上記を実行すると ~/.tool-version
が作成され、指定した Python が記録されているのを確認できます。
asdf reshim python
の記述に関しては、asdf, asdf-python の issue にも上がっているのですが、Python の auto reshim がうまくできていない様子で、これに対する私の解決策は後述します。
"reshim" は 辞書で引くと "re-shim", "shim" は「ものを水平にしたり、すき間などに入れる)詰め木」の意味で、私は shim と symbolic link を掛け合わせて「シンボリックリンクを貼り直す」の意味だと解釈しています。
一旦、シェルを再起動して、
pytohn -V
# Python 3.9.5 と出力されれば OK(2021/05/15 現在)
次に仮想環境を作ります。
mkdir test_venv cd test_venv echo "layout python" >.envrc direnv allow
これで仮想環境ができました。
試しにパッケージをインストールしてみます。
$ # test_venv で $ pip install requests (省略) $ pip list Package Version ---------- --------- certifi 2020.12.5 chardet 4.0.0 idna 2.10 pip 21.1.1 requests 2.25.1 setuptools 56.0.0 urllib3 1.26.4 $ $ cd .. $ # global な方の Python $ pip list Package Version ---------- ------- pip 21.1.1 setuptools 56.0.0
確かにディレクトリ単位で Python 環境が分けられています。
また、cd することで自動で切り替わっています。
まあまあ pyenv-virtual に近い環境が作れたと思います。
いくつかの問題の対処
global な venv 作りたい
pyenv-virtualenv からの乗り換えで一番悩んだ問題です。
私は pyenv-virtualenv を以下のような使い方をしていました。
- pyenv virtualenv 3.8.5 playground
- pyenv global playground
- pip install numpy requests ...
この playground になんでも入れて、基本的にはこれを使い、ちょっと仮想環境を切りたいときには
としていたからです。
これに対する、私の解決策としては、
これで、以前とほぼ同じ運用ができるはずです(というか、以前もこの運用でよかったと気づきました)。
他の解決法としては、ホームディレクトリ直下に .envrc
を作ってしまうことが考えられます。
pip でインストールしたコマンドが使えない
具体手には以下のような例ですね。
$ pip install uvicorn
$ uvicorn --version
zsh: command not found: uvicorn
これは、上述した asdf の reshim が自動的に実行されない問題に起因すると思われます。
私の場合は、以下のように .zshrc
に記述することで対処しました。
function __pip () { pip "$@" if [ $# -gt 0 ] && [ "$1" = "install" ]; then asdf reshim python fi } alias pip='__pip'
シンプルに pip install 後は reshim するようにしただけです。
イケてないですが、一旦よしとします。
venv 作成コマンド多すぎ
私は .zshrc
に以下のようなコマンドを定義しています
function __venv () { [ ! "$VIRTUAL_ENV" ] && echo "layout python" >>${PWD}/.envrc && direnv allow || : } alias venv='__venv'
これで、特定ディレクトリ下で venv
と実行するだけで良いです。
プロンプトの venv 名を変えられないの?
direnv の layout python には引数が渡せるので、
layout python pytohn3 --prompt venv_name
にすると、変えられるかもしれません。
私の場合、zsh のテーマに Powerline10k を使っていて、venv 下に入ると Python バージョンを勝手に表示してくれます。
ただ、逆に、上記の --prompt
で渡した値は表示してくれません。
なぜなら、venv の activate は --prompt
で渡した値を使って PS1
を書き換えるのですが、p10k は PS1
を常に書き換える& VIRTUAL_ENV
環境変数の値しか見てくれないからです。
その辺、色々ディスカッションはあるよう ですが、私の用途ではそこまで追求しないので、現状で良しとしました。
zsh テーマ、プロンプト拡張プラグインを使っていて、プロンプトに venv名を使いたい方は要注意ですね。
この辺は pyenv-virtualenv が恋しくなります(同様の問題が pyenv-virtualenv + p10k で発生しないとも言えませんが)。
シェル起動直後 venv のあるディレクトリにいるのに activate されない
私はとある経緯から、シェルを起動するときに最後に cd したパスで開くように設定しています。
if [[ -n $(echo ${^fpath}/chpwd_recent_dirs(N)) && -n $(echo ${^fpath}/cdr(N)) ]]; then autoload -Uz chpwd_recent_dirs cdr add-zsh-hook chpwd chpwd_recent_dirs zstyle ':chpwd:*' recent-dirs-max 100 zstyle ':chpwd:*' recent-dirs-default true zstyle ':chpwd:*' recent-dirs-pushd true fi if [ -f ${ZDOTDIR}/.chpwd-recent-dirs ]; then cd $(head -1 ${ZDOTDIR}/.chpwd-recent-dirs | tr -d "$'") [ -f ${PWD}/.envrc ] && (direnv reload &) || : fi
最後の fi
の直前の direnv reload
で、この課題は解決できました。
経緯
私の Python の開発環境において、以下を前提としています。
- チーム共通で開発する場合は Docker を使う
- 個人で開発するときも基本的に Docker を使う
- エディタに VSCode を使っているので、devcontainer を使う
- それでも、ローカルでサクッと実行したい時にローカル実行環境が欲しい
なので、ローカル Python はサクッと実行するときにしか使わないです。
ただ、以下のような要求を持っています。
- requests や numpy など使用頻度の高いパッケージを global 環境に入れていたい
- けど system python は汚したくない
- なんとなくだけど仮想環境を分けたくなることはありそう
そのため、今までは
- anyenv
- pyenv
- pyenv-virtualenv
を使っていました。pyenv-virtualenv は最高です。
ディレクトリ以下に .python-version があるときに、その環境に勝手に切り替えてくれます。
私は hoge/bin/activate や deactivate や pipenv shell が嫌いです。
しかし、OS アップデート&クリーンインストールした後に以下の問題にぶち当たりました。
シェルの設定ファイルにたった一行追加すれば良いことなのですが、anyenv は開発が活発ではありません。
今後、pyenv のメジャーアップデートごとに同じように対応が必要かもしれません。
「じゃあお前が anyenv に PR 出せば?」と言われても、私は弱々開発者で OSS フリーライダーなのです(今後は donation ぐらい最低限やっていきたいね)。
また、ついでに zsh 環境を見直したかったのと、新しいツールを触ってみたかったのです(これが本音だと思います、多分)。
まとめ
最終的には、anyenv pyenv virtualenv を使うよりも設定に手間が掛かっている気がします。
なので、やはり asdf を使ってみたかっただけなのだと思います。
というわけで、しばらくは asdf を使ってみようと思います。
もしかすると、また anyenv に戻るかもしれません。