+ 作るログイン
図を読み込んでいます…
1 / 21
JS から来た人の Python ― どこから来て、なぜ動くのか / 全19章

Python 13:依存を記録して共有する ― GitHub にあげて他の人が使うとき

発行
アニメモ公式
版
第1版
改訂
コマ
全21

自分のパソコンで動く tenki を GitHub に上げて、他の人が使うにはどうするのか。.venv は上げず、コードと「必要なものの一覧」だけを上げ、相手が自分の手元で venv を作り直して PyPI から入れる、という流れを3つの舞台で見せます。requirements.txt と pip freeze、pyproject.toml の dependencies と版の書き方、ロックファイル uv.lock の中身、そして uv の init・add・sync・run を実際に動かして、何のファイルができるかを確かめます。

「GitHub にあげて他の人が使う時どうするの?」

この本を書くきっかけになった疑問の1つが、これでした。

「import transformers と書いても、え、それでなんでライブラリ入るの? どこから? ローカルに保存されてる? GitHub にあげて他の人が使う時どうするの?」

前半の答えは、ここまでの章で出そろいました。ライブラリは PyPI から pip で落としてきて(第11章)、プロジェクトの .venv の中の site-packages に保存され(第12章)、import は sys.path をたどってそこから見つけます(第8章)。

残っているのが後半です。第12章で、.venv は GitHub に上げないと決めました。重く(requests だけで 16MB)、あなたのパソコンの絶対パスが焼き付いていて、他のパソコンでは動かないからです。

では、図の右側、相手のパソコンで tenki を動かすには、何をどう渡せばいいのでしょうか。

場所あるもの
あなたのパソコンtenki のコードと、requests の入った .venv
GitHubまだ何もない
PyPIrequests などライブラリの本体(第11章)
相手のパソコンPython はある。tenki も requests も無い

この章では、この4つの場所を何度も使いながら、「何を上げて、相手は何をするのか」を1段ずつ見ていきます。最後に、今よく使われる uv という道具で、同じことを数個のコマンドでやります。

※2026年9月時点(Python 3.13・uv 0.12)の情報です。この章の出力は実際に動かしたものです(作業場所のパスは /home/you・/home/friend に置き換えています)。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 1 / 21
  • 「GitHub にあげて他の人が使う時どうするの?」
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • インターネット
  • github.com/you/tenki
  • 書類
  • pyproject.toml
  • まだ空
  • 検索
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • ここで tenki を動かしたい
  • requests などの wheel
  • あなたの手元では動く。.venv は重く、あなたのパソコン専用なので上げられない
  • 相手のパソコンには requests が無い。何を渡せば、相手の手元で同じように動くのか
  • ひらめき
  • 第12章で .venv は GitHub に上げないと決めた。では相手は、requests をどうやってそろえる?

一言でいうと:料理ではなく、レシピを渡す

答えを先に言います。渡すのは「コード」と「必要なものの一覧」だけです。相手はその一覧を見て、自分のパソコンで箱(venv)を作り直し、PyPI から入れ直します。

料理にたとえると、こうなります。

たとえPython では渡すか
できあがった料理.venv(入れ終わったライブラリ)渡さない。重く、運ぶと崩れる
レシピ一覧(pyproject.toml・uv.lock)渡す。軽い文字のファイル
材料のお店PyPI相手も同じお店で買える
台所それぞれのパソコン相手は自分の台所で作る

できあがった料理(.venv)をそのまま運ぼうとすると、重いうえに崩れます(相手のパソコンでは絶対パスが合わない)。でも、レシピと材料の買える店さえ分かれば、相手は自分の台所で同じ料理を作れます。

これは JS でもまったく同じです。node_modules/ は GitHub に上げず、package.json と package-lock.json を上げて、相手が npm install で入れ直します。Python ではその「一覧」と「入れ直す道具」がいくつかあるので、順に見ていきます。

大事なのは、相手の手元に届くライブラリは、あなたのパソコンから来るのではなく、PyPI から来るということです。GitHub には、名前と版の一覧しか置きません。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 2 / 21
  • 一言でいうと:料理ではなく、レシピを渡す
  • 荷物
  • 書類
  • できあがった料理=.venv
  • レシピ=一覧(pyproject.toml・uv.lock)
  • 重い・あなたの台所(パソコン)専用・運ぶと崩れる
  • 軽い文字だけ。どの台所でも読める
  • NG
  • OK
  • 店
  • 材料はお店(PyPI)から買う
  • 更新
  • 相手の台所で作り直す
  • requests の本体は PyPI に置いてある
  • venv を作り、一覧どおりに入れる
  • ひらめき
  • 渡すのは「コード」と「必要なものの一覧」だけ。相手は一覧を見て、自分の台所で作り直す。

上げるもの・上げないもの

具体的に、tenki のフォルダのどれを上げるのかを見ます(この章の tenki は、後で見る uv init で作った形です。コードは src/tenki/__init__.py に入っています。この形は第9章の src レイアウトです)。

ファイル上げるか中身
src/tenki/__init__.py上げるあなたのコード
pyproject.toml上げる欲しいものの一覧(と、プロジェクトの説明)
uv.lock上げる実際に入れたものの正確な記録
.python-version上げる使う Python の版(3.13)
.venv/上げない入れ終わったライブラリ。あなたのパソコン専用

実際に git が管理しているファイルを git ls-files で並べると、図の右のようになります。

$ git ls-files
.gitignore
.python-version
README.md
pyproject.toml
src/tenki/__init__.py
uv.lock

.venv は出てきません。第12章で見たとおり、venv は自分の中に * だけの .gitignore を置きますし、uv が作るプロジェクトの .gitignore にも .venv と書いてあるからです。

__pycache__/(第1章のバイトコードの置き場)も、同じ .gitignore で無視されています。これもそのパソコンで作り直せるものだからです。

「作り直せるものは上げない。作り直すための情報を上げる」。これが共有の基本の考え方です。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 3 / 21
  • 上げるもの・上げないもの
  • あなたのパソコン ~/tenki/
  • GitHub
  • $ git ls-files
  • 書類
  • src/tenki/__init__.py
  • OK
  • .gitignore
  • インターネット
  • github.com/you/tenki
  • .python-version
  • 書類
  • pyproject.toml
  • OK
  • まだ空
  • README.md
  • pyproject.toml
  • 錠
  • uv.lock
  • OK
  • src/tenki/__init__.py
  • uv.lock
  • (.venv は出てこない)
  • ファイル
  • .python-version
  • OK
  • PyPI(pypi.org・第11章)
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • NG
  • 店
  • requests などの wheel
  • ひらめき
  • 上げるのはコードと一覧(pyproject.toml・uv.lock ほか)。.venv は上げない。

git push で、コードと一覧だけが GitHub へ

git push すると、図の小包のように、コードと一覧だけが GitHub に届きます。

bash
git add .
git commit -m "tenki: 気温を表示"
git push

届くのは、どれも数KBの文字のファイルです。16MB の .venv は、あなたのパソコンに残ったままです。

GitHub のページで tenki のリポジトリを開いても、requests のコードはどこにもありません。あるのは pyproject.toml の中の "requests>=2.34.2" という1行の文字と、uv.lock の中の版と URL の記録だけです。

GitHub にあるもの意味
tenki のコードあなたが書いたもの
requests という名前と版「これが要る」というメモ
requests の本体無い。PyPI にある

JS のリポジトリで node_modules/ が無いのと同じです。ライブラリの本体は、それぞれの公開場所(npm なら npmjs.com、Python なら PyPI)が持っています。

GitHub はあなたのコードの置き場、PyPI はみんなのライブラリの置き場。役割が分かれています。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 4 / 21
  • git push で、コードと一覧だけが GitHub へ
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • コード+一覧
  • インターネット
  • github.com/you/tenki
  • 書類
  • pyproject.toml
  • コードと一覧だけ
  • 検索
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • ここで tenki を動かしたい
  • requests などの wheel
  • $ git push
  • ひらめき
  • 上がるのは数KBの文字のファイルだけ。16MB の .venv は、あなたのパソコンに残る。

相手は git clone。でも、それだけでは動かない

相手は、GitHub から tenki を取ってきます。

bash
git clone https://github.com/you/tenki.git
cd tenki

図のように、相手のパソコンにはコードと一覧が届きました。でも、`.venv` はありません。requests も、どこにも入っていません。

この状態で動かそうとすると、第12章で見たおなじみのエラーになります。

$ python3 -c 'import requests'
ModuleNotFoundError: No module named 'requests'

pyproject.toml に requests と書いてあっても、Python は pyproject.toml を読んで勝手に入れてはくれません。import が見るのは sys.path のフォルダだけで、一覧のファイルは見ないからです(第8章)。

相手の手元にあるものそれで足りるか
コードある
requests が要るという一覧ある
requests の本体無い
それを入れる箱(venv)無い

つまり、相手はもう1手、「一覧を読んで、箱を作って、入れる」をやる必要があります。

JS でも、git clone した直後に node index.js すると Cannot find module 'axios' になります。npm install を打つまで node_modules/ が無いからです。まったく同じ形です。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 5 / 21
  • 相手は git clone。でも、それだけでは動かない
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • インターネット
  • github.com/you/tenki
  • 書類
  • src/tenki/__init__.py
  • 書類
  • pyproject.toml
  • コードと一覧だけ
  • コード+一覧
  • 書類
  • pyproject.toml
  • 錠
  • uv.lock
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ファイル
  • .python-version
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • requests などの wheel
  • .venv はまだ無い
  • $ git clone https://github.com/you/tenki.git → $ python3 -c 'import requests'
  • ModuleNotFoundError: No module named 'requests'(requests は一覧に名前が書いてあるだけ)
  • ひらめき
  • 相手の手元に届くのはコードと一覧だけ。requests はまだどこにも無いので、import で止まる。

相手の手元で、一覧を見て箱を作り直す

相手がやることは、図の緑の .venv/ を自分のパソコンで作ることです。

順やること使う道具
1一覧を読んで、何のどの版が要るかを知るpip / uv
2自分のパソコンに venv を作るvenv / uv(第12章)
3一覧の版を PyPI からダウンロードして、箱に入れるpip / uv(第11章)

図の小包は、PyPI から相手の .venv に届く requests 2.34.2 です。あなたのパソコンを経由していません。相手は PyPI から直接、同じ版の同じファイルを落としてきます。

できあがる .venv は、あなたの .venv とは別物です。中の絶対パスは相手のパソコンのもの(/home/friend/tenki/.venv/...)ですし、OS が違えば OS 用の部品も違います。でも、入っているライブラリの名前と版は同じです。だから tenki は同じように動きます。

この「一覧を読む・箱を作る・入れる」を、

やり方コマンドの数
pip+venv で手でやる2〜3手(venv を作る → pip install -r)
uv でやる1手(uv sync)

どちらでも、中で起きていることは同じです。まずは昔からの pip のやり方で仕組みを見て、そのあと uv に進みます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 6 / 21
  • 相手の手元で、一覧を見て箱を作り直す
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • インターネット
  • github.com/you/tenki
  • 書類
  • src/tenki/__init__.py
  • 書類
  • pyproject.toml
  • コードと一覧だけ
  • 書類
  • pyproject.toml
  • 錠
  • uv.lock
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ファイル
  • .python-version
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • requests などの wheel
  • requests 2.34.2
  • 荷物
  • .venv/(作り直した)
  • ① 一覧を読む ② 自分のパソコンに venv を作る ③ PyPI から同じ版をダウンロードして入れる
  • この3つを手でやるのが pip+venv の方法、1つのコマンドでやるのが uv sync
  • ひらめき
  • 相手のパソコンで venv を作り、一覧にある版を PyPI から入れる。あなたと同じ中身の箱ができる。

昔からの方法:pip freeze で requirements.txt を作る

いちばん古くからある一覧が、requirements.txt です。作り方は、箱に入っているものを `pip freeze` で書き出すだけです。

(.venv) $ pip freeze > requirements.txt
$ cat requirements.txt
certifi==2026.7.22
charset-normalizer==3.5.2
idna==3.20
requests==2.34.2
urllib3==2.8.0

pip freeze は「今この箱に入っているもの」を、1行に1つ「名前==版」の形で出します。> はシェルの機能で、出力をファイルに書き込みます。

相手は、それを使ってこう入れ直します。

bash
git clone https://github.com/you/tenki.git
cd tenki
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python main.py

-r は「このファイルに書いてあるものを全部入れて」という意味です。

中身
requirements.txt の正体ただの文字のファイル。pip への注文書
書式1行に1つ。名前==版 のほか、名前>=版 なども書ける
名前慣習。requirements.txt でなくても動く

この方法は今も広く使われていて、どんな Python にも pip さえあれば使えるのが強みです。uv を使っていても、uv export --format requirements.txt で同じ形のファイルを書き出せます。

ただし、freeze で作る一覧には弱点があります。次に見ます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 7 / 21
  • 昔からの方法:pip freeze で requirements.txt を作る
  • あなた
  • 相手
  • (.venv) $ pip freeze > requirements.txt
  • $ git clone https://github.com/you/tenki.git
  • $ cat requirements.txt
  • $ cd tenki
  • certifi==2026.7.22
  • $ python3 -m venv .venv
  • charset-normalizer==3.5.2
  • $ .venv/bin/pip install -r requirements.txt
  • idna==3.20
  • $ .venv/bin/python main.py
  • requests==2.34.2
  • urllib3==2.8.0
  • -r は「このファイルに書いてあるものを全部」
  • requirements.txt はただの文字のファイル。1行に1つ「名前==版」
  • 今も広く使われている。ただし、弱点がある
  • ひらめき
  • pip freeze は「今この箱に入っているもの」を版つきで書き出す。相手は pip install -r で入れ直す。

freeze の弱点:欲しかったものと、ついてきたものの区別がない

さっきの requirements.txt には5行ありました。でも、あなたが pip install したのは requests だけです。残りの4つは、requests が動くために必要で、一緒についてきたもの(依存、第11章)です。

名前なぜ入っているか
requestsあなたが直接使う
certifirequests が HTTPS の証明書の確認に使う
charset-normalizerrequests が文字コードの判定に使う
idnarequests が国際化ドメイン名の扱いに使う
urllib3requests が実際の通信に使う

freeze の一覧では、この5つが同じ顔で並びます。すると、困ることが出てきます。

困ること例
何が「本当に欲しいもの」か分からないrequests をやめたとき、idna も消してよいのか、他で使っているのか、一覧からは読めない
「版の範囲」を書けない本当は「2.34 以上ならよい」なのに、==2.34.2 に固まる。新しい版に上げたいとき、どこまで上げてよいか分からない
Python の版が書いていない3.13 の書き方をしていても、3.11 の人が入れようとして初めて失敗する

そこで今は、一覧を2枚に分けるのが普通です。

1枚目2枚目
欲しいものと範囲(人が書く)実際に入れたもの全部と正確な版(道具が書く)
pyproject.tomluv.lock などのロックファイル
JS の package.jsonJS の package-lock.json

JS を書いたことがあれば、この分け方はもう知っています。Python でも同じ形になった、ということです。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 8 / 21
  • freeze の弱点:欲しかったものと、ついてきたものの区別がない
  • ハート
  • requests
  • 荷物
  • certifi
  • 荷物
  • charset-normalizer
  • あなたが直接使う
  • requests が使う
  • requests が使う
  • 荷物
  • idna
  • 荷物
  • urllib3
  • requests が使う
  • requests が使う
  • freeze の一覧で困ること
  • たとえば
  • 全部が同じ顔で並ぶ
  • requests をやめたとき、idna を消してよいか分からない
  • 「版の範囲」を書く場所がない
  • 2.34 以上ならよい、という気持ちが == 2.34.2 に固まる
  • ひらめき
  • あなたが欲しいのは requests だけ。残り4つは requests の都合。freeze の一覧ではそれが見えない。

今の方法①:pyproject.toml に「欲しいもの」を書く

pyproject.toml は、Python のプロジェクトの説明書です。名前・版・必要な Python の版・依存するライブラリなどを書きます。JS の package.json にあたるファイルです。

toml
[project]
name = "tenki"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "requests>=2.34.2",
]
項目意味package.json では
nameプロジェクトの名前"name"
versionプロジェクトの版"version"
requires-python動く Python の版"engines": { "node": … } に近い
dependencies直接使うライブラリと版の範囲"dependencies"

dependencies に書くのは、あなたが直接 import するものだけです。certifi や idna は書きません。それは requests の説明書(requests 自身の依存の一覧)に書いてあり、入れるときに道具がたどってくれます(第11章)。

書き方は TOML という設定ファイルの形式です。[project] のような見出しの下に、キー = 値 を並べます。JSON と違ってコメント(#)が書け、最後の要素の後ろにカンマがあってもかまいません。

[project] の書き方は、Python のパッケージの決まり(packaging.python.org の pyproject.toml の仕様。元は PEP 621)で決まっていて、pip・uv・ほかの道具が同じように読めます。pyproject.toml には、ほかにも作ったものを配るための情報(第14章)や、道具の設定(第15章)も書けます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 9 / 21
  • 今の方法①:pyproject.toml に「欲しいもの」を書く
  • # pyproject.toml
  • // package.json(比べる用)
  • [project]
  • {
  • name = "tenki"
  • "name": "tenki",
  • version = "0.1.0"
  • "version": "0.1.0",
  • requires-python = ">=3.13"
  • "dependencies": {
  • dependencies = [
  • "axios": "^1.7.0"
  • "requests>=2.34.2",
  • }
  • ]
  • }
  • 書くのは「あなたが直接使うもの」と「版の範囲」だけ。ついてくる4つは書かない
  • TOML は「キー = 値」と [見出し] で書く設定ファイルの書き方。標準の決まり(PEP 621)
  • ひらめき
  • pyproject.toml は Python のプロジェクトの説明書。package.json にあたる。dependencies が一覧。

版の範囲の書き方:>=・~=・==

dependencies の "requests>=2.34.2" の >=2.34.2 の部分を、版の指定(version specifier)と呼びます。よく使う書き方を、requests の版(2.31.0・2.32.3・2.32.9・2.34.2・3.0.0 のどれが入るか)で実際に確かめたのが図の表です。

書き方意味入る版
requests>=2.322.32 以上なら何でも2.32.3・2.32.9・2.34.2・3.0.0
requests~=2.322.32 以上で、2.x のまま2.32.3・2.32.9・2.34.2
requests~=2.32.32.32.3 以上で、2.32.x のまま2.32.3・2.32.9
requests==2.32.3その版だけ2.32.3
requests>=2.32,<3範囲をはっきり書く2.32.3・2.32.9・2.34.2

~= は「互換の版」と呼ばれ、「書いた版以上で、最後の桁だけは上がってよい」という意味です。~=2.32 なら最後の桁は 32 の部分なので 2.x の中で上がってよく、~=2.32.3 なら 2.32.x の中だけです。

npm の ^1.7.0(1.x の中なら上がってよい)や ~1.7.0(1.7.x の中なら上がってよい)に近いですが、記号の意味は同じではありません。Python では ^ は使えません。

どう書くのがよいかは、何を作っているかで変わります。

作っているものおすすめ理由
アプリ(tenki のように自分で動かすもの)>= で広く書き、固定はロックに任せる版を上げたいときに一覧を直さなくてよい
ライブラリ(他の人が import するもの、第14章)広めの範囲(>= や >=…,<…)狭く書くと、使う人の他のライブラリとぶつかる

uv add requests を打つと、uv は自動で >=(今の最新) の形で書いてくれます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 10 / 21
  • 版の範囲の書き方:>=・~=・==
  • 書き方
  • 意味
  • requests で試すと(入る版)
  • requests>=2.32
  • 2.32 以上なら何でも
  • 2.32.3・2.34.2・3.0.0
  • requests~=2.32
  • 2.32 以上で、2.x のまま
  • 2.32.3・2.34.2(3.0.0 は入らない)
  • requests~=2.32.3
  • 2.32.3 以上で、2.32.x のまま
  • 2.32.3・2.32.9
  • requests==2.32.3
  • ぴったりその版だけ
  • 2.32.3
  • requests>=2.32,<3
  • 範囲をはっきり書く
  • 2.32.3・2.34.2
  • ~= は「互換の版」。最後の桁だけ上がってよい、という意味(npm の ^ や ~ に近いが、同じではない)
  • ひらめき
  • 範囲を広く書けば直しやすく、狭く書けば変わりにくい。アプリは >= で書いて、固定はロックに任せる。

今の方法②:uv.lock に「実際に入れたもの」を全部書く

pyproject.toml の >=2.34.2 は「範囲」でした。範囲だけを渡すと、相手が入れるのはその日の最新になります。あなたが 2.34.2 で動かしたのに、相手は来月出た 2.35.0 を入れるかもしれません。

そこで、「今あなたの箱に実際に入っているもの」を、もう1枚のファイルに正確に書き残します。これがロックファイルで、uv では uv.lock です。中を開くと、こうなっています(requests の部分を抜き出したもの)。

toml
[[package]]
name = "requests"
version = "2.34.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "certifi" },
    { name = "charset-normalizer" },
    { name = "idna" },
    { name = "urllib3" },
]
wheels = [
    { url = "https://files.pythonhosted.org/.../requests-2.34.2-py3-none-any.whl", hash = "sha256:...", size = ... },
]

図の番号の順に、何が書いてあるかを見ます。

順書いてあること役割
1version = "2.34.2"範囲ではなく1点の版
2source・urlどこから落とすか。PyPI のファイルの URL
3hash = "sha256:…"ファイルの中身の指紋。落としたファイルが1ビットでも違えば入れない
4certifi なども同じ形でついてきたものも全部

tenki の uv.lock は171行ありました。requests と4つの依存、そして tenki 自身のぶんです。charset-normalizer のように OS ごとに違うファイル(wheel)があるものは、Windows・macOS・Linux 用の URL と指紋が全部書いてあります。だから1つの uv.lock が、どの OS の相手にも使えます。

uv.lock は人が書くものではなく、uv が書くものです。uv のドキュメントも「手で直さないこと」「git に入れること」と書いています。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 11 / 21
  • 今の方法②:uv.lock に「実際に入れたもの」を全部書く
  • # uv.lock(抜き出し)
  • 正確な版
  • [[package]]
  • 目標
  • name = "requests"
  • 範囲ではなく1点
  • version = "2.34.2"
  • source = { registry = "https://pypi.org/simple" }
  • dependencies = [
  • リンク
  • どこから落とすか
  • { name = "certifi" }, { name = "idna" }, ...
  • PyPI の wheel の URL
  • ]
  • wheels = [
  • { url = "https://files.pythonhosted.org/…/
  • 守られている
  • 中身の指紋(sha256)
  • requests-2.34.2-py3-none-any.whl",
  • すり替えを見破る
  • hash = "sha256:…", size = … },
  • ]
  • 荷物
  • ついてきたものも全部
  • tenki の uv.lock は171行
  • ひらめき
  • ロックファイルは、ついてきた4つも含めて、正確な版・ダウンロード先・中身の指紋まで記録する。

なぜ2つ? pyproject は「幅」、uv.lock は「点」

2つのファイルの関係を、版の数直線で描いたのが図です。

pyproject.tomluv.lock
書く人あなた(または uv add)uv(自動)
書いてあるもの直接使うものと、版の範囲(幅)入れたもの全部と、正確な版(点)・URL・指紋
図では黄色の帯(2.34.2 から右ずっと)緑の点(2.34.2)
意味「この範囲なら動くはず」という約束「今はこれで動いている」という記録
JS ではpackage.json("^1.7.0")package-lock.json("1.7.4" のような1点)

相手が uv sync で入れるとき、uv は範囲の中の最新ではなく、ロックに書かれた点を入れます。だから、あなたと相手の箱の中身がそろいます。いつ・誰が入れても同じになる。これを再現できる(reproducible)と言います。

では、新しい版に上げたいときはどうするか。点を動かせばよいのです。

bash
uv lock --upgrade-package requests   # requests だけ、範囲の中の最新に点を動かす
uv sync                              # 手元の .venv を新しい点に合わせる

動いたことを確かめたら、新しい uv.lock をコミットして push します。相手は pull して uv sync すれば、同じ新しい版になります。

幅と点を分けておくと、「どこまで上げてよいか(幅)」と「今どれを使っているか(点)」を別々に管理できる。2枚に分ける理由はこれです。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 12 / 21
  • なぜ2つ? pyproject は「幅」、uv.lock は「点」
  • package.json の "^1.7.0" と package-lock.json の "1.7.4" の関係と同じ
  • pyproject.toml:requests>=2.34.2(この範囲ならよい)
  • 2.31.0
  • 2.32.3
  • 2.34.2
  • 2.35.0
  • 3.0.0
  • uv.lock:2.34.2(今はこれを入れる)
  • 相手が uv sync すると、範囲の中の最新ではなく、ロックに書かれた「点」が入る → あなたと同じ
  • 新しい版に上げたいときは uv lock --upgrade-package requests で「点」を動かし、ロックごと共有する
  • ひらめき
  • pyproject.toml は人が書く「ここまでならよい」。uv.lock は道具が書く「今はこれ」。

uv:venv と pip とロックを、1つの道具で

ここまでで、共有に必要な作業が出そろいました。

作業昔からの道具
箱を作るvenv(第12章)
PyPI から入れるpip(第11章)
欲しいものを一覧に書く手で pyproject.toml や requirements.txt を書く
実際の版を固めるpip freeze(ただし弱点あり)

uv は、これらを1つにまとめた道具です。Astral という会社が作っていて、Rust という言語で書かれているので、とても速く動きます(pip で数秒かかるものが、ほぼ一瞬で終わることが多い)。

uv のコマンド中でやっていること
uv initpyproject.toml などプロジェクトの形を作る
uv add requestspyproject.toml に書く+uv.lock を更新する+.venv に入れる
uv syncuv.lock どおりに .venv をそろえる(無ければ作る)
uv run …そろえてから、.venv の python で動かす

大事なのは、uv が新しい仕組みを持ち込んだわけではないということです。

uv が作る .venv は、第12章で見た venv と同じ形(bin/python・pyvenv.cfg・lib/python3.13/site-packages)をしています。入れるのも PyPI の同じ wheel で、site-packages に同じように展開します。pyproject.toml も標準の書き方です。中身は同じ仕組みで、手数と速さが違うだけです。

だから、ここまでの章で仕組みを知っていれば、uv で困ったときも .venv の中を開けて確かめられます。

uv そのものの入れ方は第2章を見てください。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 13 / 21
  • uv:venv と pip とロックを、1つの道具で
  • フォルダ
  • venv
  • uv
  • 中でやること
  • 箱を作る(第12章)
  • uv init
  • プロジェクトの形
  • ダウンロード
  • pip
  • 電気
  • PyPI から入れる(第11章)
  • uv add
  • 書く+固める+入れる
  • uv
  • Astral 社が作る。Rust 製で速い
  • uv sync
  • ロックどおり入れる
  • 錠
  • 一覧・ロック
  • pyproject.toml・uv.lock
  • uv run
  • 箱の python で動かす
  • ひらめき
  • uv は、これまで手でやってきた「箱を作る・入れる・一覧に書く・固める」をまとめてやる道具。

uv init:プロジェクトの形を作る

新しく tenki を始めるところから、uv でやってみます。

$ uv init tenki
Initialized project `tenki` at `/home/you/tenki`

できたファイルを並べると、こうなりました。

.gitignore
.python-version
README.md
pyproject.toml
src/tenki/__init__.py
できたもの役割
pyproject.tomlプロジェクトの説明書。dependencies = [](まだ空)
.python-version中身は 3.13。uv が箱を作るときに使う Python の版
.gitignore.venv・__pycache__/ などを無視する設定
src/tenki/__init__.pyコードの入れ物。main() 関数が1つ入っている
README.md説明を書くためのファイル(空)
.git/git のリポジトリも作られる

pyproject.toml には、ほかに [project.scripts] と [build-system] という見出しもあります。tenki = "tenki:main" は「tenki というコマンドで tenki パッケージの main 関数を呼ぶ」という意味で、uv run tenki で動かせるのはこのおかげです。この2つは、tenki を配れる形にする第14章で詳しく見ます。

uv init の直後には、まだ .venv も uv.lock もありません。必要になった瞬間に uv が作るからです。次の uv add で、それが起きます。

※uv 0.12 より前の uv init は、src/ を作らずに main.py を1つ置く形でした。古い記事と見た目が違うのはそのためです。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 14 / 21
  • uv init:プロジェクトの形を作る
  • $ uv init tenki
  • [project]
  • Initialized project `tenki` at `/home/you/tenki`
  • name = "tenki"
  • $ cd tenki && find . -not -path './.git*'
  • version = "0.1.0"
  • ./.python-version
  • requires-python = ">=3.13"
  • ./README.md
  • dependencies = []
  • ./pyproject.toml
  • ./src/tenki/__init__.py
  • [project.scripts]
  • tenki = "tenki:main"
  • できたもの
  • 役割
  • [build-system]
  • .python-version
  • 使う Python の版(3.13)
  • build-backend = "uv_build"
  • .gitignore
  • .venv などを無視
  • [project.scripts] と [build-system] は第14章で説明(抜き出し)
  • ひらめき
  • pyproject.toml・.python-version・.gitignore と、src の下にコードの入れ物ができる。

uv add requests:書く・固める・入れる を一度に

tenki に requests を足します。

$ uv add requests
Using CPython 3.13.7
Creating virtual environment at: .venv
Resolved 6 packages in 167ms
Installed 6 packages in 5ms
 + certifi==2026.7.22
 + charset-normalizer==3.5.2
 + idna==3.20
 + requests==2.34.2
 + tenki==0.1.0 (from file:///home/you/tenki)
 + urllib3==2.8.0

この1行で、図の番号の3つが起きました。

順起きたこと確かめ方
1pyproject.toml の dependencies に "requests>=2.34.2" が足されたcat pyproject.toml
2uv.lock ができた(6つ分の正確な版・URL・指紋)cat uv.lock
3.venv が作られ、6つが入ったls .venv/lib/python3.13/site-packages

pip のやり方と比べると、「入れる」と「一覧に書く」が1手になったのが大きな違いです。pip では、入れた後で一覧を書き直すのを忘れると、「自分の手元では動くのに、相手のところでは動かない」になります。uv add はそれを起こしません。

.venv/lib/python3.13/site-packages/ を開くと、第12章と同じように requests/・requests-2.34.2.dist-info/ などが並んでいます。uv が作っても、置き場の形は同じです(uv の .venv には pip は入っていません。入れるのは uv の仕事だからです)。

6つ目の tenki==0.1.0 は、tenki 自身です。「ソースのフォルダを直接見る形(編集可能)」で箱に入っていて、コードを書き換えるとすぐ反映されます。この仕組みは第14章で見ます。

ライブラリをやめるときは uv remove requests で、一覧・ロック・箱の3つから一度に消えます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 15 / 21
  • uv add requests:書く・固める・入れる を一度に
  • $ uv add requests
  • Using CPython 3.13.7
  • 書類
  • pyproject.toml に1行
  • Creating virtual environment at: .venv
  • "requests>=2.34.2" が足される
  • Resolved 6 packages in 167ms
  • Installed 6 packages in 5ms
  • + certifi==2026.7.22
  • uv.lock を作る・書き直す
  • + charset-normalizer==3.5.2
  • 錠
  • + idna==3.20
  • 6つ分の正確な版と指紋
  • + requests==2.34.2
  • + tenki==0.1.0 (from file:///home/you/tenki)
  • + urllib3==2.8.0
  • 荷物
  • .venv を作って入れる
  • 無ければ作る。中身は第12章の venv
  • tenki 自身も「編集可能」として箱に入る(第14章)
  • ひらめき
  • 一覧に1行足し、uv.lock を作り、.venv に入れる。入れると書くが1手になる。

uv run:確かめてから、箱の python で動かす

コードを書いたら、uv run で動かします。src/tenki/__init__.py の中身はこうです。

python
import requests

URL = "https://api.open-meteo.com/v1/forecast"


def main() -> None:
    params = {"latitude": 35.68, "longitude": 139.77, "current": "temperature_2m"}
    data = requests.get(URL, params=params, timeout=10).json()
    print(f"東京の気温: {data['current']['temperature_2m']}℃")
$ uv run tenki
東京の気温: 19.6℃

uv run は、動かす前に毎回、図の番号の順に確かめます(uv のドキュメントより)。

順確かめること
1pyproject.toml と uv.lock が合っているか(ずれていればロックを直す)
2.venv の中身が uv.lock どおりか(ずれていれば入れ直す)
3.venv の python でコマンドを動かす

3の「.venv の python で」は、実際に確かめられます。

$ uv run python -c 'import sys; print(sys.executable)'
/home/you/tenki/.venv/bin/python3

activate していないのに、箱の python が動いています。第12章の「activate しなくても、箱の python をパスで指せば同じ」を、uv が代わりにやっているわけです。

uv run は uv run python main.py のように python に続けても、uv run pytest のように箱に入った道具の名前でも使えます。JS の npm run や npx に近い使い心地です。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 16 / 21
  • uv run:確かめてから、箱の python で動かす
  • 検索
  • pyproject.toml と uv.lock は合っているか
  • $ uv run tenki
  • 東京の気温: 19.6℃
  • $ uv run python -c 'import sys;
  • 更新
  • .venv の中身は uv.lock どおりか
  • print(sys.executable)'
  • /home/you/tenki/.venv/bin/python3
  • 再生
  • .venv/bin/python でコマンドを動かす
  • activate は要らない。第12章の「パスで直接指す」を uv がやっている
  • ひらめき
  • uv run は毎回ずれを確かめて合わせてから、.venv の python で動かす。

相手の手元:git clone → uv sync → uv run

では、相手のパソコンに戻ります。uv があれば、相手がやることは3つだけです。

$ git clone https://github.com/you/tenki.git
$ cd tenki
$ uv sync
Using CPython 3.13.7
Creating virtual environment at: .venv
Resolved 6 packages in 1ms
Installed 6 packages in 4ms
 + certifi==2026.7.22
 + charset-normalizer==3.5.2
 + idna==3.20
 + requests==2.34.2
 + tenki==0.1.0 (from file:///home/friend/tenki)
 + urllib3==2.8.0
$ uv run tenki
東京の気温: 19.6℃

これは実際に、別の場所に clone して、uv のキャッシュも空の状態から試した結果です。あなたの uv add のときと、まったく同じ版が入りました。uv.lock に書かれた点どおりだからです。

相手の uv sync がやったこと使った情報
Python 3.13 を選んだ.python-version と requires-python
相手のパソコンに .venv を作った(第12章の venv と同じ形)
6つの版と、どこから落とすかを決めたuv.lock
PyPI から落とし、指紋を確かめて入れたuv.lock の URL と sha256

図の3つの舞台を見直すと、ライブラリの本体は PyPI → 相手の `.venv` と動き、あなた → GitHub → 相手 の道を通ったのは文字のファイルだけです。

これが「GitHub にあげて他の人が使う時どうするの?」への答えです。コードと一覧だけを上げる。相手は clone して、自分の手元で箱を作り直して入れる。

相手が uv を使っていなくても、pyproject.toml は標準の書き方なので、pip でも pip install . のように読めます(プロジェクトを入れるこの形は第14章)。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 17 / 21
  • 相手の手元:git clone → uv sync → uv run
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • インターネット
  • github.com/you/tenki
  • 書類
  • src/tenki/__init__.py
  • 書類
  • pyproject.toml
  • コードと一覧だけ
  • 書類
  • pyproject.toml
  • 錠
  • uv.lock
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ファイル
  • .python-version
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • requests などの wheel
  • requests 2.34.2
  • 荷物
  • .venv/(作り直した)
  • git clone https://github.com/you/tenki.git → cd tenki → uv sync → uv run tenki
  • uv sync で .venv ができ、requests==2.34.2 など6つが入る → 東京の気温: 19.6℃
  • ひらめき
  • 相手が打つのは3つだけ。uv sync が venv を作り、uv.lock どおりの版を PyPI から入れる。

ロックとずれていたら止める:uv sync --locked

uv sync と uv run は親切で、pyproject.toml と uv.lock がずれていると、ロックを書き直してから入れます。手元で作業しているときは、これが便利です。

でも、自動テスト(CI)や本番のサーバーで動かすときは、これが困ることがあります。誰かが pyproject.toml だけを直して、uv.lock を上げ忘れていたら、その場でロックが作り直され、あなたが確かめたのと違う版が入るかもしれないからです。

そこで、--locked を付けます。実際に、pyproject.toml だけを変えて試すとこうなりました。

$ uv sync --locked
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.

hint: To update the lockfile, run `uv lock`.
付け方ずれていたとき使う場面
uv syncロックを書き直してから入れる手元での普段の作業
uv sync --lockedエラーで止まるCI・本番。ロックどおりであることを保証したいとき
uv sync --frozenロックを確かめずに、そのとおり入れるロックが正しいと分かっているとき

JS の npm install(必要なら lock を書き換える)と npm ci(lock とずれていたら止まる)の関係と同じです。

「ずれていたら黙って直す」と「ずれていたら止める」を、場面で使い分ける。これも再現できることを守るための仕組みです。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 18 / 21
  • ロックとずれていたら止める:uv sync --locked
  • # pyproject.toml だけ直して、uv.lock を更新し忘れた
  • $ uv sync --locked
  • 守られている
  • error: The lockfile at `uv.lock` needs to be updated,
  • but `--locked` was provided.
  • なぜ止める?
  • 確かめたのと違う版を入れないため
  • hint: To update the lockfile, run `uv lock`.
  • 付け方
  • ずれていたとき
  • uv sync
  • ロックを書き直してから入れる(手元の普段使い)
  • uv sync --locked
  • エラーで止まる(CI・本番。npm ci と同じ考え)
  • ひらめき
  • CI や本番では --locked を付ける。一覧とロックがずれていたら、黙って直さずにエラーで止まる。

JS ではどうか:npm と並べる

ここまでの道具を、npm と並べます。

やることnpmpip+venvuv
欲しいものの一覧package.jsonrequirements.txt などpyproject.toml
入れたものの記録package-lock.jsonpip freeze の結果uv.lock
置き場node_modules/.venv/(自分で作る).venv/(自動で作る)
足すnpm install axiospip install requests(一覧は自分で)uv add requests
一覧どおり入れるnpm installpip install -r requirements.txtuv sync
ずれたら止めるnpm ci(これに当たるものは無い)uv sync --locked
動かすnpm run・npxactivate して pythonuv run

見比べると、uv は npm にかなり近い使い心地です。JS から来た人が uv を気に入りやすいのは、このためです。

違いは2つあります。

1つ目は、Python では「箱(venv)」が別の道具だったことです。Node.js は node_modules/ をファイルの場所から探すので、箱の出入りがありません(第12章)。uv は箱を自動で作り、uv run で箱の python を使うことで、この違いを見えにくくしています。

2つ目は、Python の版もそろえることです。uv は .python-version を見て、必要なら Python そのものも入れます。npm は Node.js の版まではそろえません(nvm などの別の道具を使います)。

仕組みを知っていれば、どの道具を使っていても「一覧」「記録」「箱」の3つを探せば、何が起きているか分かります。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 19 / 21
  • JS ではどうか:npm と並べる
  • やること
  • npm
  • pip+venv
  • uv
  • 欲しいものの一覧
  • package.json
  • requirements.txt など
  • pyproject.toml
  • 入れたものの記録
  • package-lock.json
  • pip freeze の結果
  • uv.lock
  • 置き場
  • node_modules/
  • .venv/(自分で作る)
  • .venv/(自動)
  • 足す
  • npm install axios
  • pip install requests
  • uv add requests
  • 一覧どおり入れる
  • npm install
  • pip install -r …
  • uv sync
  • ずれたら止める
  • npm ci
  • (無い)
  • uv sync --locked
  • 動かす
  • npm run / npx
  • activate して python
  • uv run
  • ひらめき
  • 役割はほぼ1対1。違うのは、Python では venv(箱)が別の道具だったこと。uv はそこも含めてまとめる。

よくある間違い:「自分の手元では動くのに」

共有したときに一番よく起きるのが、「自分の手元では動くのに、相手のところでは動かない」です。原因はたいてい、手元の箱と一覧がずれていることです。

やってしまったこと何が起きるか直し方
uv pip install や pip install で入れた手元の箱には入るが、pyproject.toml と uv.lock に書かれない。相手の箱には入らないuv add で入れる
uv.lock をコミットしなかった相手は範囲の中の最新を入れる。あなたと版がずれるuv.lock も必ずコミットする
.venv をコミットしてしまったリポジトリが重くなり、相手のパソコンでは壊れているgit rm -r --cached .venv で管理から外す
uv.lock を手で直した指紋や依存の記録と食い違う手で直さず uv lock で作り直す
Python の版が違う3.13 で書いたものが 3.11 で動かないrequires-python と .python-version を書いておく

1つ目は特に気づきにくいので、確かめ方を覚えておきましょう。

bash
uv sync      # 一覧にないものは、箱から消される
uv run tenki # これで動けば、相手のところでも動く

uv sync は、既定ではuv.lock に無いものを箱から取り除きます。一覧に書き忘れたライブラリがあれば、ここで消えてエラーになるので、「相手のところで起きること」を自分の手元で再現できます。

どうしても pip のやり方で共有するなら、pip install の後に pip freeze > requirements.txt をし直して、それもコミットします。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 20 / 21
  • よくある間違い:「自分の手元では動くのに」
  • やってしまったこと
  • 何が起きるか
  • 直し方
  • uv pip install で入れた
  • 箱には入るが、一覧に書かれない
  • uv add で入れる
  • uv.lock をコミットしない
  • 相手は範囲の中の最新を入れる
  • uv.lock も必ず上げる
  • .venv をコミットした
  • 重い・相手のパソコンで壊れる
  • git rm -r --cached .venv
  • uv.lock を手で直した
  • 指紋と合わず壊れる
  • uv lock で作り直す
  • Python の版が違う
  • 3.13 の書き方が 3.11 で動かない
  • requires-python と .python-version
  • ひらめき
  • 手元で動いて相手で動かないのは、たいてい「一覧に書かずに入れた」か「ロックを上げ忘れた」。

まとめ:上げるのはレシピ。相手は自分の台所で作る

この章で見たことをまとめます。

言葉正体
共有の基本.venv は上げない。コードと一覧だけを上げ、相手が自分の手元で作り直す
ライブラリの本体GitHub には無い。相手も PyPI から直接落とす
requirements.txtpip freeze で書き出す「名前==版」の一覧。pip install -r で入れる
pyproject.tomlプロジェクトの説明書。dependencies に直接使うものと版の範囲(幅)を書く
版の指定>=(以上)・~=(互換の版)・==(その版だけ)
uv.lock入れたもの全部の正確な版・URL・指紋(点)。uv が書き、git に入れる
uvvenv+pip+ロックを1つに。init・add・sync・run
相手の手順git clone → uv sync → uv run tenki
CI・本番uv sync --locked(ずれていたら止まる)

図の3つの舞台で言えば、文字のファイルはあなた → GitHub → 相手と流れ、ライブラリの本体は PyPI → 相手の `.venv` と流れる。この2本の道を分けて考えれば、「GitHub にあげて他の人が使う時どうするの?」はもう迷いません。

ここまでの共有は、相手がtenki のソースを clone して使う形でした。でも、requests を使うとき、あなたは requests のリポジトリを clone しませんでした。pip install requests(や uv add requests)の1行で済みました。

では、tenki も同じように、`pip install tenki` の1行で誰でも使えるようにするには、何が要るのでしょうか。次の第14章では、tenki をパッケージにして、GitHub から直接、そして PyPI から配る方法を見ていきます。

  • JS から来た人の Python 13 ― 依存を記録して共有する
  • 21 / 21
  • まとめ:上げるのはレシピ。相手は自分の台所で作る
  • あなたのパソコン ~/tenki/
  • GitHub
  • 相手のパソコン ~/tenki/
  • 書類
  • src/tenki/__init__.py
  • インターネット
  • github.com/you/tenki
  • 書類
  • src/tenki/__init__.py
  • 書類
  • pyproject.toml
  • コードと一覧だけ
  • 書類
  • pyproject.toml
  • 錠
  • uv.lock
  • 錠
  • uv.lock
  • ファイル
  • .python-version
  • PyPI(pypi.org・第11章)
  • ファイル
  • .python-version
  • ライブラリの本体
  • 荷物
  • .venv/(requests ほか)
  • 店
  • requests などの wheel
  • 荷物
  • .venv/(作り直した)
  • あなた:コード+pyproject.toml+uv.lock を push(.venv は上げない)
  • 相手:clone → uv sync(venv を作り、uv.lock どおり PyPI から入れる)→ uv run
  • ひらめき
  • 次の疑問:相手が clone しなくても、pip install tenki の1行で使えるようにするには?(第14章)

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]