スクエニ ITエンジニア ブログ

俺流!PEP668とうまくやっていく方法

PEP668は突然やってきた

pythonのスクリプト書いてるときに、OSに入っていないpythonのモジュールが必要になったらどうしてますか?

いままで、自分は細かいこと考えず、すぐ使えればいいやと割り切って

$ pip3 install --user ほしいモジュール名

って、いつもやってました(笑)。まあ、見るからに、野蛮ですね。

ところが、先日久しぶりにDebian sidにはない無いモジュール(pycoingecko)が必要になって、いつものようにpip3を実行しました。そしたら突然のエラーですよ!

$ pip3 install --user pycoingecko 
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.11/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation 
or OS distribution provider. You can override this, at the risk of breaking your
 Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

なんじゃこりゃー! メッセージの通り、PEP668がヒントとか言われてます。

早速、pipのupstreamのNEWS.rstを確認すると、pip ver 23.0で搭載された機能のようです。

ここでは、自分流のやり方でPEP668と付き合う方法を書いてみます。

対策その1:余裕の無い人向け

 納期に余裕がないから一番早い方法をタノム!という叫びが聞こえた気がします。そういう人には--break-system-packagesオプション一発で今までどおりのインストールができます。ここではpycoingeckoを導入してみます。

$ pip3 install --break-system-packages --user pycoingecko
Collecting pycoingecko
  Using cached pycoingecko-3.1.0-py3-none-any.whl (8.8 kB)
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (from 
pycoingecko) (2.28.1)
Installing collected packages: pycoingecko
Successfully installed pycoingecko-3.1.0
$ pip3 list --user
Package     Version
----------- -------
pycoingecko 3.1.0

もちろんアンインストールも同じオプションをつけるだけでいつも通りです!

$ pip3 uninstall --break-system-packages pycoingecko 
Found existing installation: pycoingecko 3.1.0
Uninstalling pycoingecko-3.1.0:
  Would remove:
    /home/yours/.local/lib/python3.11/site-packages/pycoingecko-3.1.0.dist-info
/*
    /home/yours/.local/lib/python3.11/site-packages/pycoingecko/*
Proceed (Y/n)? Y
  Successfully uninstalled pycoingecko-3.1.0
$ pip3 list --user
...何も表示されない...

--break-system-packagesオプションがあって、良かったですね!

対策その2:メッセージの内容に従う人向け

優雅に美しくPEPを尊重という方は、そもそもpip3 install --userなんて野蛮なことは普段からしてないはずなので大丈夫なはずです。しかしながら、「悔い改めて今日から私はPEP668準拠!ジーク・PEP!」という人もいるはずです。そういう人向けの対策を書きます。ここでは、pip3のメッセージに従い、OS側付属のpythonに標準搭載のvenvを利用するようにします。

 手順は次のとおりです。

  1. 開発予定のプログラムを収めるディレクトリを作成します。
$ mkdir your-project
$ cd your-project
  1. pip3 install --userでインストールしてしまったモジュールの一覧をバックアップしつつ、消去します。こちらをしたくない人は本手順をスキップしてください。その場合、後でこれらのモジュールが思わぬところで利用されてしまい、不具合になるかもしれません。
# pip3 installで過去にいれてしまったモジュール一覧
$ pip3 list --user
Package       Version
------------- -------
cachetools    5.3.0
distlib       0.3.6
filelock      3.11.0
platformdirs  3.2.0
pyproject_api 1.5.1
tox           4.4.11
virtualenv    20.21.0
# モジュール一覧をrequirement.txtに残しつつ消す
$ pip3 freeze --user | tee requirements.txt | \
	xargs pip3 uninstall -y --break-system-packages 
Found existing installation: cachetools 5.3.0
Uninstalling cachetools-5.3.0:
  Successfully uninstalled cachetools-5.3.0
Found existing installation: distlib 0.3.6
...中略...
Found existing installation: tox 4.4.11
Uninstalling tox-4.4.11:
  Successfully uninstalled tox-4.4.11
Found existing installation: virtualenv 20.21.0
Uninstalling virtualenv-20.21.0:
  Successfully uninstalled virtualenv-20.21.0
$ pip3 list --user
...跡形もなく消えます... 
  1. venvをカレントディレクトリの.venvディレクトリに導入して起動します。 なお、ls .venv/binしてpip3やpython3が入っていることを確かめてください。
$ python3 -m venv --system-site-packages --clear --prompt 'your-project' \
	--upgrade-deps $(pwd)/.venv
$ ls .venv/bin/
Activate.ps1  activate.csh   pip   pip3.11  python3
activate      activate.fish  pip3  python   python3.11
$ source .venv/bin/activate
(your-project)$ command -v python3
/home/yours/your-project/.venv/bin/python3 <--- venv付属のpython3になっている
(your-project)$ command -v pip3
/home/yours/your-project/.venv/bin/pip3 <--- venv付属のpip3になっている
  1. 作成したrequirements.txtに基づくモジュールをインストールします。
(your-project)$ pip3 install -r requirements.txt
Collecting cachetools==5.3.0
  Using cached cachetools-5.3.0-py3-none-any.whl (9.3 kB)
Collecting distlib==0.3.6
  Using cached distlib-0.3.6-py2.py3-none-any.whl (468 kB)
...中略...
Requirement already satisfied: colorama>=0.4.6 in /usr/lib/python3/dist-packages
 (from tox==4.4.11->-r ./requirements.txt (line 6)) (0.4.6)
Requirement already satisfied: pluggy>=1 in /usr/lib/python3/dist-packages (from
 tox==4.4.11->-r ./requirements.txt (line 6)) (1.0.0+repack)
Installing collected packages: distlib, pyproject_api, platformdirs, filelock, c
achetools, virtualenv, tox
  Attempting uninstall: platformdirs
    Found existing installation: platformdirs 2.6.0
    Not uninstalling platformdirs at /usr/lib/python3/dist-packages, outside env
ironment /home/yours/prog/bitbank/.venv
    Can't uninstall 'platformdirs'. No files were found to uninstall.
Successfully installed cachetools-5.3.0 distlib-0.3.6 filelock-3.11.0 platformdi
rs-3.2.0 pyproject_api-1.5.1 tox-4.4.11 virtualenv-20.21.0
# 無事インストールできたかを確認
(your-project)$ pip3 list | fgrep -f <(cut -f 1 -d = ./requirements.txt)
cachetools         5.3.0
distlib            0.3.6
filelock           3.11.0
platformdirs       3.2.0
pyproject_api      1.5.1
tox                4.4.11
virtualenv         20.21.0
  1. 準備できたので、いったんvenv環境から抜けます。
(your-project)$ deactivate
$ <--- venv環境から抜け出すとプロンプトが元通り

以上で、

  1. your-projectディレクトリ内でsource .venv/bin/activateをする度に、今までpip3 install --userで適当に導入していたモジュールが、そのまま使える
  2. deactivateと唱えると何も導入されていない状態になる

が実現できました!

また、venv環境で作成したスクリプトの冒頭は、#!/usr/bin/env python3と記載しておけば、activateすればvenvに導入されたpython3のツリーが利用されて実行されますので便利ですね!試しにやってみます。

$ cd your-project
$ source .venv/bin/activate
(your-project)$ cat > where_is_python.py <<__HERE
> #!/usr/bin/env python3
> import sys
> print(sys.executable)
> sys.exit(0)
> __HERE
(your-project)$ chmod 755 where_is_python.py 
(your-project)$ ./where_is_python.py 
/home/yours/your-project/.venv/bin/python3 <--- venvのpython3が利用されている
(your-project)$ deactivate  <--- venvから抜けると、
$ ./where_is_python.py
/usr/bin/python3 <--- OS側のpython3が利用される

見ての通り、venvをactivateしていればvenv側のpython3が、venvをdeactivateしていればOS側のpython3が利用されます。

また、venvに導入したモジュールのドキュメントを読みたいときは、activateした状態で、

(your-project)$ python3 -mpydoc venvでpip3 installしたモジュール名
例:(your-project)$ python3 -mpydoc tox

で読めます。

venv使いに改心したのに、いちいちsource .venv/bin/activateするのが面倒!という人向けには、direnvがあるようです。自分は未評価ですが、Debianパッケージにもなっている(apt install direnvで入る)ので、dirvenvのwikiあたりでも読んで導入すると便利かもしれません。ここらあたりは、様々な流派が存在するようですので、好みの方法でactivateしてみてください。

対策その3: その他python3仮想環境の人向け

様々な理由によりvenvではなく、他のpythonの仮想環境を使っている人はどうすればいいのでしょう?例えばpyenvとか、~envとか…数々のpythonの仮想環境が存在します。

今回、pip3に施されたPEP668対応の仕組みは、以下の条件が揃うと発動します。

発動条件1:

pip3 installの操作がOSに入っているpython3環境に影響を与える。具体的にはpip3 installのオプションにて

  • --dry-run--reportを同時に指定していない、かつ、
  • --root--target--prefix--break-system-packagesのいずれも指定していない。

発動条件2:

現在のpython3は、pythonの仮想環境の元で起動していない。具体的には、import sysにて取得できるsys.prefixsys.base_prefixの値が同じであり、sys.real_prefixが定義されていない。

発動条件3:

import sysconfigして取れるsysconfig.get_path("stdlib")のパスにEXTERNALLY-MANAGEDファイルがある。

というわけで、上述いずれかの条件を満たさないpythonの仮想環境の元であれば、PEP668は発動しません。ご自分のお使いのpythonの仮想環境で、pip3がPEP668のエラーを起こすようであれば、上述いずれかの条件を満たしていないか?を確認してみてください。

以下に簡単なテスト方法を記載しておきます。PEP668 Affected!と出たらpip3 installはPEP668のエラーを発動してしまうpythonの仮想環境ということになります。各pythonの仮想環境で実行するとpip3がそのまま使える、あるいは、使えないがわかると思います。

$ python3 -c 'import sys;import sysconfig;import os;print("PEP668 affected!") \
   if sys.prefix == sys.base_prefix and not hasattr(sys,"real_prefix") and \
   os.path.isfile(os.path.join(sysconfig.get_path("stdlib"), \
   "EXTERNALLY-MANAGED")) else print("PEP668 not affected!")' 

# PEP668のエラーがpip3 installで出る環境
PEP668 Affected!
# PEP668のエラーがpip3 installで出ない環境
PEP668 not Affected!

PEP668とはなんぞや

先に対策を書いて気持ちがすっきりしたので、ここからはPEP668について書きます。

PEP668に定義があります。内容としては、 OSが用意するパッケージのコマンド(dpkg/apt/dnf等)の管理外で、python用のモジュール管理コマンド(例:pip3等)を不用意に使うとOS側のpythonの環境に悪影響を及ぼしてしまうのを防ぎたいというものです。提案されているのは、OS側のpython3のディレクトリにEXTERNALLY-MANAGEDファイルをおいたら、そこはpython用のモジュール管理コマンドの影響から保護してね!という内容です。

OSに詳しい人なら普段から疑問に思ってた!という件ですし、PEP668はこちらの問題に真正面から取り組むものではあるのですが、そうじゃない人に自分もうまく説明できる自信がありません。ここは何でも知ってるChatGPTに問い合わせてみました。

ChatGPTにPEP668を説明してもらいました

おお、for 5 years old boyは凄い威力です!おもちゃ(pythonモジュールの事)をわけましょうね!というとてもわかり易い内容ですね。PEP668を5歳児に説明ができると言い張る時点で、ChatGPTは自分の中ではシンギュラリティ超えしてます。

pipxは?ねえ、pipxは?

冒頭のpip3の表示するメッセージをちゃんと読むと、pythonアプリケーション導入するならpipxを推すという内容が書かれていると思います。

pipxは、

  • pipx install hogeとやると、$(HOME)/.local/pipx/venvs/hogeという名前のvenv環境が生成され、hogeアプリケーションに必要なモジュールがインストールされます。hogeアプリケーション本体はこちらのvenvの環境で稼働するように調整されて$(HOME)/.local/binに入ります。パッケージの名前がvenvの環境名になるので、pipx install barとやると、既存のhogeアプリケーションとはまったく違うvenv環境が新たに生成されてインストールされます。このため、すでにpipxで導入したhogeプリケーションと、barアプリケーションはvenv環境がそもそも違うので競合しません。
  • もし、追加で別のモジュールfooをhogeアプリケーションに導入したい場合は、pipx inject hoge fooとやると、hogeのvenv環境にfooモジュールを追加できます。

という、python製のアプリケーション導入に向いたpythonのパッケージインストーラとなります。いちいちactivateしなくてもアプリケーション専用のvenv環境の元で実行されるので確かに便利です。

なにかpython製のアプリケーションをpip3 install --userでいつもインストールしていた人は、代わりにpipxで導入するようにすると便利かもしれません。

ここでは、例としてbpytopアプリケーションを導入してみます。

$ sudo apt install pipx
...pipxが導入される...
$ pipx install bpytop
...中略...
$ tree -L 4 ~/.local  ← .local/bin/にコマンドが導入され、.local/pipx以下にvenv環境がbpytopの名前で展開される
.local/
├── bin
│   └── bpytop -> /home/yours/.local/pipx/venvs/bpytop/bin/bpytop
└── pipx
    ├── logs
    │   └── cmd_2023-04-11_07.18.32.log
    ├── shared
    │   ├── bin
    │   │   ├── Activate.ps1
    │   │   ├── activate
    │   │   ├── activate.csh
    │   │   ├── activate.fish
    │   │   ├── pip
    │   │   ├── pip3
    │   │   ├── pip3.11
    │   │   ├── python -> python3
    │   │   ├── python3 -> /usr/bin/python3
    │   │   ├── python3.11 -> python3
    │   │   └── wheel
    │   ├── include
    │   │   └── python3.11
    │   ├── lib
    │   │   └── python3.11
    │   ├── lib64 -> lib
    │   └── pyvenv.cfg
    └── venvs
        └── bpytop   <---ここがアプリケーション名で新たに作られる。
            ├── bin
            ├── include
            ├── lib
            ├── lib64 -> lib
            ├── pipx_metadata.json
            └── pyvenv.cfg

17 directories, 16 files
$ head ~/.local/bin/bpytop ← ~/.local/pipx/venvs/bpytop以下の
                           ← venvをうまく利用するようにSHELL BANG(行頭1行目)が修正済
#!/home/yours/.local/pipx/venvs/bpytop/bin/python
# -*- coding: utf-8 -*-
import re
import sys

$ ~/.local/bin/bpytop
...bpytopが動く...

終わりに

突然やってきたPEP668と仲良くする方法を掲載しました。

PEP668は、OS側のpythonも尊重しつつ、開発もうまくやってほしい!ということだと思いますので、今後は面倒がらずにpythonの仮想環境と仲良くしていくのも良いと思います。

この記事を書いた人

記事一覧
SQUARE ENIXでは一緒に働く仲間を募集しています!
興味をお持ちいただけたら、ぜひ採用情報ページもご覧下さい!