Python install managerはバージョン26.0でパッケージが提供するコマンドやスクリプトへのショートカットも生成するようになりました。その使い勝手や仕組みについて、今回は調べてみました。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
どうもHPかわさきです。
『Windowsで「pipが通らない」を減らす Python install manager 26.0のグローバルショートカット』は読んでいただけましたか? 今回はグローバルショートカットについて、Python install manager 25.xと26.0との違いや、どんな仕組みでpipコマンドが使えるようになったのか、なんでパッケージのインストール時にはショートカットが生成されないのかなどについて調べてみました。
『Windowsで「pipが通らない」を減らす Python install manager 26.0のグローバルショートカット』でも触れましたが、Python install manager 26.0ではインストール済みのパッケージに関して、それらのentry_points.txtファイルを参照し、パッケージが提供するコマンドやスクリプトをコマンドラインから起動できるようにする「グローバルショートカット」を生成するようになりました。
筆者が試したところでは、グローバルショートカットは%LOCALAPPDATA%\Python\binディレクトリに作成されます(このディレクトリを以下ではグローバルショートカットディレクトリと呼びます)。
このディレクトリ自体はPython install manager 25.0の時点でも作成されるようになっていました。Python install managerをインストールすると次のようにグローバルショートカットディレクトリを環境変数PATHに追加するかどうかが問い合わせられます(ただし、以下はバージョン25.2のもので、バージョン25.0でもそうだったかは確認できませんでした)。
Python install manager 25.xではグローバルショートカットディレクトリには、Python処理系(Python 3.14、Python 3.13など)へのショートカットが置かれますが、パッケージが提供するコマンドやスクリプトへのショートカットは置かれません。「py install --refresh」コマンドでグローバルショートカットディレクトリを更新してもパッケージ由来のコマンドやスクリプトへのショートカットは生成されません。
以下はPython install manager 25.2がインストールされた環境で、python3.14コマンド、pip3コマンドなどを実行しているところです(グローバルショートカットディレクトリは環境変数PATHに登録済み)。
python3.14コマンドを実行すると、ちゃんと起動できます(グローバルショートカットディレクトリの名前を変更したら、python3.14コマンドを起動できなくなったので、これがこのディレクトリのおかげなことも確認済みです)。が、pip3コマンドを実行してもエラーになってしまいます。「py install --refresh」コマンドを実行してもダメなことが確認できました。
このとき(Python install manager 25.2)のグローバルショートカットディレクトリの内容は次のようになっています。
なるほど。pip3コマンドがないことが分かります(後で、バージョン26でどうなっているかを見てもらいましょう)。
これに対して、Python install manager 26.0ではどうかを確認したのが以下です。
ご覧の通り、python3.14コマンドもpip3コマンドも実行できました。グローバルショートカットディレクトリがどうなっているかというと以下のようになっていました。
このようにpipコマンドやpip3コマンドへのショートカットがあります。Python install manager 26ではこのようにpythonコマンドだけではなく、パッケージのインストール時に提供される各種のコマンドやスクリプトへのショートカットも作られるようになり、「グローバルな環境」(つまり、PCにインストールされたPython環境)にそのままパッケージをインストールする場合のパッケージ使用の利便性が向上しました。
というわけで、グローバルにパッケージをインストールしてみます。ここではPythonのフォーマッタであるblackを例としました。以下はblackのインストール直後にblackコマンドを実行できるかどうかを試したものです。
「black --version」コマンドを呼び出せません。が、「python -m black --version」コマンドなら大丈夫です。これがグローバルショートカットの自動生成の(現時点における)弱点です。つまり、pipによるパッケージのインストールとショートカットの生成が同期されないことです。
ショートカットを生成するには「py install --refresh」コマンドを実行します。これにより、グローバルショートカットディレクトリが更新され、パッケージのインストール後、生成されていなかったショートカットが生成されます。
なんでこんなことになっているかというと、Python install manager 26.0でのグローバルショートカット生成はentry_points.txtファイルを頼りにしているからです。というわけで、ちょっとその辺についても見ておきましょう。
entry_points.txtファイルというのは、パッケージをインストールする際に同時にインストールされるメタデータファイルです(Python install managerでのみ使われるものではありません)。
pipを例に取れば、pipのインストール後に「pip install ……」のようなコマンドラインを実行できるようにする、つまり各種コマンドを呼び出す「エントリポイント」を記述しているのがentry_points.txtファイルといえます。このファイルはPythonをインストールした先にある「Lib\site-packages\パッケージ-バージョン.dist-info」ディレクトリに作成されます(パッケージごとにentry_points.txtファイルが存在するということです)。
pip 25.3であれば、「Lib\site-packages\pip-25.3.dist-info」ディレクトリです。
以下にpipのentry_points.txtの内容を示します。
これはpip3コマンドとpipコマンドが呼び出されたら、pip._internal.cli.mainのmain関数でこれを処理することを意味しています。このmain関数では与えられた引数を基に「pip install」や「pip list」のようなコマンドを処理すると思ってください(詳細は省略します)。
Python install managerは「py install」コマンドの実行時に、インストール済みのパッケージのdist-infoにあるentry_points.txtファイルを参照し、対応するショートカットを生成していると考えられます。
このような振る舞いをすることから(というか、Python install managerがパッケージのインストールの全てを監視しているわけにはいかないので)、インストーラーによるパッケージのインストールでは「py install --refresh」が必要になるのでしょう。
『Python install manager 26.0』では「My hope is that one day installers (such as pip) will learn to run this command themselves, but until then, users will just have to get used to it」と述べられています。テキトーに訳すと「いつかはpipなどのインストーラーが「py install --refresh」を実行するようになってくれればいいけれど、それまではユーザーがそうすることに慣れるしかないねぇ」といったところでしょう。そういう日が来てくれるといいですね。
では、実際に作成されるショートカットとはどんなものなのでしょう。実はグローバルショートカットディレクトリにある多くのEXEファイルはハードリンクとなっていて、同一のファイルです(pythonw系の実行ファイルはそれらで同一になっています)。
EXEファイル以外には、例えば、「python.exe.__target__」のようなファイルもあります。
__target__ファイルの中身は「%LOCALAPPDATA%\Python\pythoncore-バージョン\python.exe」のようになっています(ここでは環境変数LOCALAPPDATAを使っていますが、実際にはフルパス)。ビックリするのはpython.exe.__target__ファイルでもpip3.exe.__target__ファイルでも同じ内容になっていることです(pythonw系はそっちで同じ)。
「pythoncore-バージョン」には「pythoncore-3.14-64」などが入ります。これはおそらく、Python install managerによってインストールされるPythonを意味しています。どのバージョンをデフォルトとして使っているかによって、この値は変わるかもしれません。
要するに、ハードリンクとなっているEXEファイルはいわゆる「shim」というヤツです(複数要素をつなぎ合わせる小さな実行ファイル)。実体は、__target__ファイルで参照されるPythonインタプリターに処理を引き渡すものです。
では、どんな処理を引き渡すのかというと、「python」「python3.14」などを実行するのであれば、コマンドライン引数を含めて全てが__target__ファイルで参照されるインタプリターに渡されるのだと推測しています。では、「pip list」「pip3 install」「black --version」などが呼び出されたときにはどうなるのでしょう。
そうした場合に重要になるのが以下に示す__script__.pyファイルです。
このときには、__target__ファイルで指定されるPythonインタプリターに「__script__.py」ファイルが渡されるのでしょう。ちなみにpip.exe.__script__.pyファイルの内容は次のようになっています。
import sys
# Clear sys.path[0] if it contains this script.
# Be careful to use the most compatible Python code possible.
try:
if sys.path[0]:
if sys.argv[0].startswith(sys.path[0]):
sys.path[0] = ""
else:
open(sys.path[0] + "/" + sys.argv[0], "rb").close()
sys.path[0] = ""
except OSError:
pass
except AttributeError:
pass
except IndexError:
pass
# Replace argv[0] with our executable instead of the script name.
try:
if sys.argv[0][-14:].upper() == ".__SCRIPT__.PY":
sys.argv[0] = sys.argv[0][:-14]
sys.orig_argv[0] = sys.argv[0]
except AttributeError:
pass
except IndexError:
pass
from pip._internal.cli.main import main
sys.exit(main())
これはsys.pathとargvを調整し、pip._internal.cli.mainモジュールのmain関数を呼び出しています。先ほどのentry_points.txtに書かれていたエントリポイントが呼び出されるということですね。
では、shimが__script__.pyファイルを呼び出して、そこからpip._internal.cli.main.main関数が呼び出されるというのがホントかどうかを試してみましょう。といっても、ここではpip.exe.__script__.pyファイルとpip._internal.cli.main.main関数の先頭に「print('hello from pip.exe.__script__.py')」と「print('hello from pip._internal.cli.main.main')」の2行を書き加えて「pip list」コマンドを実行するだけです。以下はその実行結果です。
というわけで、どうやらそのように動いているようです。
Python install manager 26.0はグローバルにインストールされているPythonの(パッケージの)使い勝手を改善してくれるツールということがよく分かりました。entry_points.txtについては、筆者も深掘りはできていませんが、これについては時間があればもうちょっと勉強したいところです。
Copyright© Digital Advantage Corp. All Rights Reserved.