目次
AIに簡単なPythonスクリプトの実行例を頼むと、たまに uv run python script.py という形で返ってきますよね。自分が普段書いてるのは uv run script.py の方なので、python を挟む書き方はAIが冗長にしてるだけなのか、それとも意味があるのかが気になって調べました。
PEP 723のメタデータを読むかが違う
uv run script.py と uv run python script.py の違いは、PEP 723 のインラインメタデータ(.py 先頭に書く # /// script 枠で依存と Python バージョンを宣言する記法)を読みに行くかどうかです。前者は読む。後者は python を経由するのでメタデータを無視して、いま使っている venv のまま実行します。
依存を .py 1ファイルに閉じ込めて配る使い方をしたいなら uv run script.py でないと動きません。逆に、プロジェクトの一部として書いた .py を実行するだけなら結果はだいたい同じです。
たとえば次のように、httpx への依存を1ファイルに宣言したスクリプトを考えます。
# /// script
# requires-python = ">=3.11"
# dependencies = ["httpx"]
# ///
import httpx
print(httpx.get("https://example.com").status_code)
uv run script.py で叩くと、uvが先頭の宣言を読んで httpx 入りのキャッシュ済み venv を裏で組み、その中で実行します。同じファイルを uv run python script.py で叩くと、# /// script 枠はただのコメントとして扱われ、現在の venv に httpx が入っていなければ ModuleNotFoundError で落ちます[1]。
普段の用途では前者だけで足ります。python を挟む書き方は、後段で書く別の場面で意味を持ちます。
AIが uv run python と書く3つの理由
公式ドキュメントもZenn/Qiitaの入門記事も uv run script.py を主に使っているのに、AI出力では uv run python script.py が混じります。背景には、AIの学習データと書き癖の偏りが重なっていそうです。
学習データに古い python 直接実行が多い
uvが普及する前のPythonコードは、ほぼ全て python script.py という形で書かれていました。AIがuv対応のコマンドを書くとき、既存パターンへの最小編集として「先頭に uv run を足す」やり方を取りやすく、結果として uv run python script.py の形がそのまま残ります。
PEP 723の知識量が薄い
PEP 723(インラインスクリプトメタデータ)は2024年に標準化された比較的新しい仕様で、AIの学習データに登場する量自体が少なめです[2]。# /// script 枠を読んでvenvを組むuvの挙動を「そういう機能がある」程度には知っていても、デフォルトの実行コマンドにまでは反映されていない、という温度感です。
「明示的に書く方が安全」というバイアス
Pythonの "explicit is better than implicit" 規範に引っ張られて、AIは曖昧さが減る方の書き方を選びがちです。uv run script.py だと「これは何で動いてるんだろう」と読み手が一瞬迷うのに対し、uv run python script.py は「Pythonを起動して script.py を渡す」が見た目で明示されているように映る。実際はPEP 723をskipする別の挙動なんですが、表面的にわかりやすそうな形に流れます。
それでも python を挟むのが正しい場面
ここまでだと「AIが冗長にしているだけ」に聞こえますが、uv run python の形が必要な場面はちゃんと存在します。.py を動かすだけの普段使いでは前者で十分で、Python自身に何か指示したいときに後者を使う、という棲み分けです。
Pythonインタプリタにフラグを渡したいとき
最適化モード -O、警告制御 -W、開発フラグ -X dev などをPython自身に渡したいときは python を挟みます。
uv run python -O script.py
uv run python -W error script.py
uv run python -X dev script.py
uv run -O script.py のように書くと、-O はuv自身のフラグと解釈されて意図通りに動きません。uv run -- python -O script.py という -- で区切る書き方も使えますが、uv run python -O の方が短く済みます[3]。
対話REPLと -c のワンライナー
インタプリタを対話モードで開いたり、その場で1行コードを評価したいときも python を挟みます。
uv run python
uv run python -c "import sys; print(sys.version)"
これは uv run script.py では代用できません。スクリプトファイル名がないと、uvは「何を実行すべきか」を判定できないからです。
インラインメタデータを意図的にskipしたいとき
誰かが書いたスクリプトに # /// script 枠が残っていて、でも自分のプロジェクトの依存で動かしたいときに、uv run python 経由で枠を無視できます。あまり使う場面はないですが、デバッグの逃げ道として知っておくと役に立ちます。
