pylock.tomlとuv.lockの違い—PEP 751をpipとuvで実測する

同じ依存関係から uvpip にそれぞれ pylock.toml を書き出させたら、16パッケージと15パッケージに分かれました。差分は colorama 1件。PEP 751 はツール非依存のロックファイル形式ですが、何を書き出すかまでは揃えていません。

目次

requirements.txt が固定しきれなかったもの

pip freeze の出力に無い情報

pip freeze が書くのは名前とバージョンだけ。どのインデックスから取ったか、どの環境向けか、ファイルのハッシュがいくつか、どれも残りません。--require-hashes でハッシュは足せますが、その書式は pip 独自で、他のツールは解釈できない。

Poetry は poetry.lock、PDM は pdm.lock、uv は uv.lock。「依存を固定する」という同じ目的に対して、エコシステムごとに別形式が3つ以上並んでいました。CI のインストーラを差し替えると、ロックファイルごと作り直しになります。Go の go.sum もバージョン固定ではなくハッシュ台帳でした(Go Modulesの仕組みを実測する—go.sumがロックファイルではない理由)。

PEP 751 が決めたのはフォーマットだけ

PEP 751 は 2025年3月31日に Final になりました。決めたのはファイル形式で、依存解決のアルゴリズムには踏み込んでいません。仕様の “File Name” セクションはファイル名を pylock.toml、または正規表現 r"^pylock\.([^.]+)\.toml$" に一致する名前に限定します。pylock.ci.toml は有効、lock.toml は無効。

pylock.toml のトップレベルキーと [[packages]]

必須キーは2つ

packaging.python.org の pylock.toml 仕様が定義するトップレベルキーは次のとおりです。

キー必須役割
lock-version必須現行は "1.0"。インストーラは互換性を確認する
created-by必須生成したツール名。"uv" / "pip" がそのまま入る
requires-python任意対応する Python バージョン指定子
environments任意サポートする環境マーカーの配列
extras / dependency-groups任意選択可能な extras と依存グループ名
[[packages]]必須インストール候補になるパッケージの配列

配布物のテーブルは排他

1パッケージにつき [packages.vcs][packages.directory][packages.archive][[packages.wheels]][packages.sdist] のうち1系統しか書けません。インストーラは vcs → directory → archive → wheels → sdist の順で評価し、最初に見つかった系統を使います。

dependencies は解決に使ってはいけない

Tools MUST NOT use this information when doing installation; it is purely informational for auditing purposes.

仕様の [[packages.dependencies]] セクションにある一文。依存グラフを書き残すのは構わないが、インストーラはそれを読んで解決してはならない、という指定です。pylock.toml は解決済みの平坦なリストであって、依存グラフではない。だから [[packages]] の並び順にも意味がありません。

uv export と pip lock は同じ名前で別物を書く

検証環境は uv 0.11.25pip 26.2.1Python 3.14.6、macOS(arm64)。httpx==0.28.1rich==14.1.0 を直接依存に持つプロジェクトを /tmp/pylock-demo に作って比較しました。

uv export の出力

uv export --format pylock.toml -o pylock.toml
head -12 pylock.toml
# This file was autogenerated by uv via the following command:
#    uv export --format pylock.toml -o pylock.toml
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.14"

[[packages]]
name = "anyio"
version = "4.15.0"
index = "https://pypi.org/simple"
sdist = { url = ".../anyio-4.15.0.tar.gz", upload-time = 2026-09-02T21:46:36Z, size = 276504, hashes = { sha256 = "b5c620ed..." } }
wheels = [{ url = ".../anyio-4.15.0-py3-none-any.whl", upload-time = 2026-09-02T21:46:35Z, size = 131908, hashes = { sha256 = "7ecd9937..." } }]

83行 / 7,660バイト、11パッケージ。indexsizeupload-time まで残り、sdist と wheel の両方が記録されます。typing-extensions にだけ marker = "python_full_version < '3.15'" が付きました。

pip lock の出力

pip lock --index-url https://pypi.org/simple \
  "httpx==0.28.1" "rich==14.1.0" -o pylock.pip.toml
WARNING: pip lock is currently an experimental command.
It may be removed/changed in a future release without prior warning.

生成物は123行 / 4,014バイト、同じく11パッケージ。行数は uv より多いのにバイト数は約半分です。uv がインラインテーブルで1行に詰めるのに対し、pip は [[packages.wheels]] を展開して書くため行が増える。そのうえで sdistindexsizeupload-time を一切書きません。

[[packages]]
name = "anyio"
version = "4.15.0"

[[packages.wheels]]
name = "anyio-4.15.0-py3-none-any.whl"
url = ".../anyio-4.15.0-py3-none-any.whl"

[packages.wheels.hashes]
sha256 = "7ecd9937369ffce8bba0b5ccb9b3a9507b101b0ed50256aecfbab27e6c2acb99"

差分は marker と sdist

pytest==8.4.2 を足すと差がはっきりします。uv は coloramamarker = "sys_platform == 'win32'" 付きで含め、pip は macOS で解決した結果しか書かないため colorama が消える。冒頭の16対15はこれです。

項目uv export –format pylock.tomlpip lock
パッケージ数(pytest込み)1615
環境マーカー残す残さない
sdist記録する記録しない(wheelのみ)
requires-python>=3.14書かない
index / size / upload-timeありなし
想定用途マルチプラットフォーム生成環境と同一プラットフォーム

pip lock --help はこれを明示しています。

The generated lock file is only guaranteed to be valid for the current python version and platform.

dependency-groups キーはどちらのツールも書かない

PEP 751 のトップレベルには dependency-groupsdefault-groups があります。開発用依存を別枠で持ち、インストール時に選べる設計。ここが一番引っかかりました。

uv export –all-groups で pytest がフラットに混ざる

uv add --group dev "pytest==8.4.2" を実行してから書き出します。

uv export --format pylock.toml --all-groups -o pylock.groups.toml
python -c "
import tomllib, pathlib
d = tomllib.loads(pathlib.Path('pylock.groups.toml').read_text())
print('トップレベル:', list(d.keys()))
print('dependency-groups:', d.get('dependency-groups'))
print('pytest入り?:', any(p['name'] == 'pytest' for p in d['packages']))
"
トップレベル: ['lock-version', 'created-by', 'requires-python', 'packages']
dependency-groups: None
pytest入り?: True

pytestpluggyiniconfig は入っているのに、dependency-groups キーは出力されない。[[packages]] 側にもグループを示すマーカーが付きません。httpxpytest が同じ平面に並び、区別する手がかりがゼロになります。

pip lock –group dev も同じ

pip lock --index-url https://pypi.org/simple --group dev -o pylock.pipgroup.toml .
トップレベル: ['lock-version', 'created-by', 'packages']
dependency-groups キー: None
pkgs: 16  # 'pytest' 'pluggy' 'iniconfig' を含む。'demo-app' 自身も混在

pip も --group を受け付けますが、結果は同じくフラット。ついでにローカルプロジェクト demo-app 自身が [packages.directory] として混ざります。

本番だけ入れる手段が残っていない

Docker のマルチステージビルドで、最終段に本番依存だけ入れる書き方が通りません。

# 想定どおり動かない書き方
COPY pylock.toml .
RUN pip install -r pylock.toml   # pytest ごと入る

グループを分けたいなら、書き出しの時点で分割しておくしかありません。仕様がファイル名に pylock.*.toml を許しているので、用途ごとに複数置けます。

uv export --format pylock.toml --no-dev -o pylock.prod.toml
uv export --format pylock.toml --all-groups -o pylock.dev.toml

1ファイルで両方を賄う設計は、生成側が dependency-groups を書くようになるまで成立しません。Pythonのパッケージ管理はuvで統一すべき?pip・poetryとの違いで触れた uv.lock はグループを保持したままなので、uv sync --no-dev は今も普通に動きます。捨てられない理由がここ。

pylock.toml からインストールする手順

pip 26.1 以降の -r pylock.toml

pip は 25.1pip lock26.1pip install -r pylock.toml を追加しました。どちらも experimental 扱いのままです。uv が書いたファイルを pip に食わせてみます。

pip --python /tmp/v_uv/bin/python install -r pylock.toml
WARNING: Using pylock.toml as a requirements source is an experimental feature.
It may be removed/changed in a future release without prior warning.
Successfully installed anyio-4.15.0 certifi-2026.7.22 h11-0.16.0 httpcore-1.0.9
 httpx-0.28.1 idna-3.19 markdown-it-py-4.2.0 mdurl-0.1.2 pygments-2.21.0
 rich-14.1.0 typing-extensions-4.16.0

警告は出るものの、他ツールが生成したファイルがそのまま通りました。uv pip sync pylock.toml も同じ11パッケージで完了します。相互運用は成立している。

marker はインストール時に評価される

colorama 入りの16パッケージ版を macOS で入れると、導入されるのは15個。

導入数: 15
['Pygments', 'anyio', 'certifi', 'h11', 'httpcore', 'httpx', 'idna',
 'iniconfig', 'markdown-it-py', 'mdurl', 'packaging', 'pluggy', 'pytest',
 'rich', 'typing_extensions']   # colorama は入らない

仕様の “Installation” セクションが定めるインストーラの手順は、lock-version の確認 → requires-python の照合 → marker の評価 → 配布物の選択、という並びです。sys_platform == 'win32' が偽になり、colorama は候補から外れる。逆に言えば、marker を持たない pip 生成のファイルは Windows 用の情報を最初から持ちません。

ハッシュを1文字変えると止まる

httpx の wheel ハッシュの先頭4文字を 0000 に書き換えて、キャッシュを無効にしてインストールします。

pip --python /tmp/v_tamper/bin/python install --no-cache-dir -r pylock.tampered.toml
ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.
If you have updated the package versions, please update the hashes.
Otherwise, examine the package contents carefully; someone may have tampered with them.
    httpx==0.28.1 from .../httpx-0.28.1-py3-none-any.whl:
        Expected sha256 0000fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
             Got        d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad

PEP 751 がインストーラにハッシュ検証を義務づけているため、--require-hashes は要りません。pylock.toml を渡した時点で pip が検証します。

2026年9月時点の対応ツール

ツール生成インストール備考
uvuv export --format pylock.tomluv pip sync / uv pip install -r0.6.15 で先行対応。主形式は uv.lock のまま
pippip lock(25.1〜, experimental)-r pylock.toml(26.1〜, experimental)生成物は生成環境専用
PDM対応対応ネイティブ形式と併存
Pipenv対応対応2026系で提供
Poetry未対応未対応issue #10356 で追跡中

サジェストの “pep 751 poetry” の答えは、2026年9月時点では未対応。poetry.lock を置き換えるか export を先に出すかで議論が続いています。

まとめ

  • PEP 751 は2025年3月31日に Final。lock-version"1.0"、必須キーは lock-versioncreated-by[[packages]]
  • uv export は marker と sdist を残すマルチプラットフォーム向け、pip lock は生成環境専用。同じ依存で16対15に割れる
  • dependency-groups キーは uv も pip も書き出さない。本番/開発を分けるなら pylock.prod.toml のように書き出し側で分割する

相互運用そのものは動きます。uv が書いたファイルを pip がハッシュ検証つきで入れられて、marker も正しく評価される。ただ「1ファイルで全部賄う」段階には届いていないので、当面は uv.lock を主、pylock.toml を配布用のエクスポートとして扱うのが現実的です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次