長いことPython環境の管理には conda のお世話になってきたのですが、最近は venv が主流になりつつあるそうです。
主たる理由は標準ライブラリに組み込まれたこと、それから依存関係で不具合を起こしやすかった機械学習ライブラリの多くが、pip install
で十分解決できるようになってきたことかと思います。
そこで、一念発起してvenvへの意向を試みたのですが、condaとの微妙なシンタックスの違いで慣れない部分がありました。
特に、
python -m venv env/path/name # 任意の場所に仮想環境を作れる
source env/path/name/bin/activate # アクティブ化するときはパスを入力する
のように、アクティブ化するときにパス名を入力するのが面倒だな、と思いました。
conda
だと、仮想環境はすべてminiconda/envs
内に作られ、どこからでも conda activate <envname>
で使えます。
プロジェクトフォルダ内に環境を作っていれば良いのですが、汎用的な環境を使い回す場面も多いので、小回りがほしいと思いました。
そういうわけで、venv
をconda
っぽく使う方法について考えました。
tl;dr
下記のシェルスクリプトを .bashrc
や .bash_profile
など、端末開始時に読み込まれるファイルに追加することで、
-
venvc <envname>
で仮想環境が作られます。実体のフォルダは$VENVROOT
以下に作られます。 -
venvc2
,venvc3
でPythonのバージョンを選べます。 -
venva
` で仮想環境をアクティブにします。 -
venvd
で仮想環境を非アクティブ化します。deactivate
と同じです。 -
venvr <envname>
で仮想環境を削除します。つまり、実体のフォルダを消します。 -
venvl
で仮想環境の一覧を表示します。つまり、$VENVROOT
直下のフォルダを表示します。 - 通常通り
venv
コマンドを使って任意の場所に仮想環境を作ることには干渉しません。
# choose the default venv location
VENVROOT="$HOME/venv"
function _venvc() {
# internally used by venv3 and venv2
# $1 : env name
# $2 : python version
if [ -z "$1" ]
then
echo "Provide env name; e.g. venvc$2 <envname>"
else
python$2 -m venv "$VENVROOT/$1" && echo "Created env at $VENVROOT/$1"
fi
}
function venvc2() { _venvc "$1" 2; }
function venvc3() { _venvc "$1" 3; }
alias venvc=venvc3
# because i use python3 most of the time
function venva() { source "$VENVROOT/$1/bin/activate"; }
alias venvd=deactivate
function venvr() {
# $1 : env name
# $2...: options to rm, in case -f or sort is needed
envname="$1"
shift
if [ -z "$envname" ]
then
echo "Provide env name; e.g. vencr <envname>"
else
rm -r $@ "$VENVROOT/$envname" && echo "Removed $VENVROOT/$envname"
fi
}
function venvl() {
# $1...: options to ls
ls $@ "$VENVROOT"
}
詳しい説明
venvの通常の使い方
仮想環境の作成
venv
を用いて仮想環境を作るには、次のコマンドを実行します。
$ python3 -m venv myenv
# ここで python3としているのは、多くのOSでデフォルトのpythonが python2になっていることが多いためです
# python2 や python にすると、対応するバージョンをベースにした仮想環境になります。
すると、カレントディレクトリ内に、myenv
というフォルダが作られ、そのなかに仮想環境関係のファイルが作られます。確認してみます。
$ ls myenv/bin -l
# -rw-r--r-- 1 user user 2189 8月 28 07:23 activate
# -rw-r--r-- 1 user user 1241 8月 28 07:23 activate.csh
# -rw-r--r-- 1 user user 2393 8月 28 07:23 activate.fish
# -rw-r--r-- 1 user user 8834 8月 28 07:23 Activate.ps1
# -rwxrwxr-x 1 user user 254 8月 28 07:23 easy_install
# -rwxrwxr-x 1 user user 254 8月 28 07:23 easy_install-3.8
# -rwxrwxr-x 1 user user 245 8月 28 07:23 pip
# -rwxrwxr-x 1 user user 245 8月 28 07:23 pip3
# -rwxrwxr-x 1 user user 245 8月 28 07:23 pip3.8
# lrwxrwxrwx 1 user user 7 8月 28 07:23 python -> python3
# lrwxrwxrwx 1 user user 16 8月 28 07:23 python3 -> /usr/bin/python3
-
myenv/bin/python
->myenv/bin/python3
->/usr/bin/python3
というようにシンボリックリンクが貼られています。つまり、この環境においてpython
を実行すると、/usr/bin/python3
が実行されるということです。 -
pip
はシンボリックリンクではないようです。サイズが小さいからでしょうか。
仮想環境の利用
作成した仮想環境を利用するには、次のコマンドを実行します。
$ source myenv/bin/activate
試しに pythonのバージョンを確認してみます。
(myenv) $ python -V
# Python 3.8.10
(myenv) $ which python
# /***/***/myenv/bin/python
確かに python3
が使われ、また、コマンドは仮想環境内のシンボリックリンクを指しているようです。
つまり、これは実行ファイルの探索パスが変わっているということです。調べてみます。
(myenv) $ echo "$PATH"
# /***/***/myenv/bin:********
確かに、仮想環境内のbin/
フォルダが最初に探索される場所になっています。これにより、仮想環境内のPythonが優先的に使われるようになります。
仮想環境の主たる目的はパッケージ群の管理なので、仕組みが気になります。これも確認しておきます。
(myenv) $ python -c "import sys; print(sys.path)"
# ['', '/usr/lib/python38.zip', '/usr/lib/python3.8',
# '/usr/lib/python3.8/lib-dynload', '/***/***/myenv/lib/python3.8/site-packages']
最初にローカルフォルダ、次にグローバルなパッケージ、最後に仮想環境内のパッケージ、という順になっています。ということは、グローバル環境をベースに、それに仮想環境内でパッケージを追加するイメージのようです。
仮想環境の非アクティブ化
仮想環境の利用を終了するには、次のコマンドです。
(myenv) $ deactivate
$ python -V
# Python 2.7.18
非アクティブ化すると (myenv)
の文字が消え、 Pythonのバージョンもデフォルトのものに戻りました。
仮想環境を削除する
特に削除コマンドは用意されていないようです。仮想環境の実体はフォルダに過ぎないので、これを削除することで替えるのが現状のようです。
場合によっては、-f
オプションが必要なこともあるかもしれません。
$ rm -r myenv
# or
$ rm -rf myenv
condaっぽくする方法
venv
による作業はコマンドの実行すぎないので、それをラッピングすればconda
っぽくできそうです。
つまり、共通のルートディレクトリ(VENVROOT=$HOME/venv
としている部分)を指定し、その中に仮想環境を作るようコード化すればよいです。
少し考慮が必要なのが、アクティブ化の処理です。
$ source myenv/bin/activate
この source
というのは、実行シェルと同じ環境でスクリプトを実行することを意味します。こうしている理由は、$PATH
変数など、実行元の環境への変更を伴う作業だからです。そのため、
$ bash myenv/bin/activate
では目的は達成されません。bash
から呼ぶと、スクリプトは別プロセスで実行されるので、その途中で起こった変数変更は元の環境へは影響しないためです。
ここで行いたいことは、venva <envname>
とすると、 source $VENVROOT/<envname>/bin/activate
が実行元のシェルで実行される、ということです。それを実現する1つの方法が、関数を作ることです。というのも、シェルスクリプトの関数は、同一シェルで実行されるからです。つまり、
function venva() {
source "$VENVROOT/$1/bin/activate"
}
という関数を作れば良いということです。
そこで、全てのコマンドをシェル関数にラッピングして、それを .bashrc
に追加することで、端末で常に使える状態にしています。使っているとコマンドとの差違が見えませんが、ファイルの実体がないため、which venva
が何も返さないという特徴があります。
その他の実装については、シェルの関数を書いているだけなので説明を割愛をします。