Python のクリーンな環境ベストプラクティス
$ source .venv/bin/activate
これをやり忘れて
$ pip install 何かのパッケージ
これをすると環境が壊れる or 汚れるので面倒です。 何かしらのパッケージ群が ~/.local/
にインストールされてしまいます。 特に実行可能なものが ~/.local/bin/
に入ってしまうのはかなり嫌なので避けたいです。
そうするとグローバルな pip
っていらなくね?と思うようになってきました。 venv
をアクティベートしてから初めて pip
が使えるようになるのが環境をクリーンに保つための正しいプロセスではないでしょうか?
なのでこのスクラップではグローバルな pip
がないクリーンな Python 環境を検討してみます。 システムは WSL Ubuntu 22.04 をベースに考えます。 macOS は分かりません。
TL;DR
- システムのパッケージマネージャーで
python3-pip
をインストールするのはやめよう - グローバルなツール系 CLI アプリは
pipx
でインストールしよう -
pipx
はpipx.pyz
でインストールしよう - 複数バージョンの Python を入れる時も
pip
をシステムグローバルにインストールしないように注意しよう。 ソースビルドする時は--with-ensurepip=no
でpip
のブートストラップは回避できます
システム Python に pip をインストールしない
Linux 系のシステムには多くの場合 Python インタプリタはデフォルトでインストールされています。
$ python3 -V
Python 3.10.12
しかし pip は入っていないことが多いです。 pip コマンドがないというよりも pip
というモジュールが Python の中に存在していません。
$ pip
bash: pip: command not found
$ python3 -m pip
/usr/bin/python3: No module named pip
この状態であれば、良い状態です。 追加でシステムの pip をインストールしないでおきましょう
APT 系 (Ubuntu など) でも DNF 系 (Fedora) でも大体の場合 python3-pip
というパッケージ名で別で配布されていると思います。 環境構築時点で python3-pip
をインストールしてしまっているのなら削除してしまいましょう。
$ sudo apt remove python3-pip
pipx
pipx の必要性
Python で開発しているならプロジェクト管理に Poetry やコードのフォーマットに Ruff を使うことがあると思います。 それらのコマンドラインツール類は PyPI で配布されており pipx
がベストプラクティスです。
ツール類は Python コード内で使う訳じゃないので、ユーザーグローバル環境内に 1 つあればいいです。 でもツールをグローバルにインストールすると、そのツールが依存しているパッケージまでもがグローバル環境にインストールされてしまいます。 「ツールはコマンドとして使えるけど、環境は別にしてくれる」それが pipx
の役目です。
では pipx をインストールする方法を検討してみましょう。
pip で pipx をインストールする
グローバルな pip は違法です 🚨
システムで pipx をインストールする
Ubuntu 22.04 の場合は APT で pipx をインストールできます。
$ apt info pipx
Package: pipx
Version: 1.0.0-1
... しかし pipx のバージョンがかなり古いです 😨
pipx 1.0.0 は現在から 2 年以上前のバージョン [1] です。 私は 1.4.2 でサポートされた PEP 723 に関する機能 [2] を利用したいのですが、Ubuntu 22.04 が提供している pipx では利用不可のようです。
zipapp を pipx で実行する
pipx が配布している zipapp [3] を利用します。 スタンドアロンで pipx を実行できます。
$ wget https://github.com/pypa/pipx/releases/latest/download/pipx.pyz
$ python3 pipx.pyz -h
usage: pipx.pyz [-h] [--quiet] [--verbose] [--global] [--version]
{install,uninject,inject,upgrade,upgrade-all,uninstall,uninstall-all,reinstall,reinstall-all,list,interpreter,run,runpip,ensurepath,environment,completions}
...
pipx in pipx
zipapp のスタンドアロンな pipx を使って pipx をインストールします。
$ python3 pipx.pyz install pipx
...
$ pipx list
...
package pipx 1.5.0, installed using Python 3.10.12
- pipx
これで pipx により pipx をインストール できました。 pipx.pyz
自体は不要なので削除してもいいでしょう。
これのメリットは pipx upgrade-all
で pipx 自体も最新版にアップデートできることです。 しかしドキュメントページをよくみると pipx 側としてはこれは not recommended としているようです。
pipx uninstall-all
や pipx reinstall-all
などで pipx を破壊してしまう可能性があるからということです。 なので pip で pipx をインストールしましょうと書いてあります。 しかし私はこのデメリットよりグローバルに pip をインストールするデメリットの方が遥かに大きいと思っています。 そのような事象があることに注意しつつ pipx-in-pipx を利用するのがベストではないでしょうか。
もし気になるようであれば pipx-in-pipx はやめておきましょう。 先ほどの pipx.pyz
を削除せずに実行ファイルにするのが回避手段です。
$ chmod +x pipx.pyz
$ mv pipx.pyz ~/.local/bin/pipx
$ pipx -h
usage: pipx [-h] [--quiet] [--verbose] [--global] [--version]
{install,uninject,inject,upgrade,upgrade-all,uninstall,uninstall-all,reinstall,reinstall-all,list,interpreter,run,runpip,ensurepath,environment,completions}
...
ただしこの場合は pipx 自体のアップデートだけがちょっと面倒になりそうです。
Multiple Python versions
私の場合はライブラリ開発で複数バージョンの Python が必要です。 pip 抜きで複数バージョンの Python を利用する方法を検討します。
Homebrew
Homebrew を利用するとビルドなしで複数バージョンの Python バイナリが手に入ります。 しかし試したところ brew install python
だと pip もグローバルにインストールされてしまいました。 その回避方法は不明なので使えません。
deadsnakes PPA
deadsnakes PPA は Ubuntu 用の 3rd party なリポジトリです。 メインストリームの Python バージョン以外 (Ubuntu 22.04 なら Python3.10 以外) のバイナリを提供してくれます。 メインストリームと同様に python3.x-pip
をインストールしなければ pip はグローバルになりません。
$ sudo add-apt-repository -P deadsnakes
$ sudo apt install python3.8 python3.9 python3.11 python3.12
ただしこれは 3rd party によるビルドなので最新バージョンの配布に時差あるかもしれません。 また仕様上の問題ですがメインストリームの Python 3.10 は PPA から配布できないので Python 3.10 のみマイナーが古い場合があります。
Source build
公式からソースをダウンロードして自前でビルドします。 一次情報源なので、リリースされたと同時に最新バージョンを利用することもできます。
依存関係などのインストールやビルド時間などは若干面倒です。
$ sudo add-apt-repository -s
$ sudo apt build-dep python3.10
$ wget https://www.python.org/ftp/python/3.yy.zz/Python-3.yy.zz.tgz
$ tar -xf Python-3.yy.zz.tgz
$ cd Python-3.yy.zz
$ ./configure --prefix="$HOME/.local/" --with-ensurepip=no
$ make
$ make altinstall
./configure
に --with-ensurepip=no
を指定することで pip がブートストラップされません 🪄