Python ファイル1つで依存ライブラリ管理もできるポータブル実行スクリプトを作る

2026-05-01 02:40 (2 hours ago)
Python ファイル1つで依存ライブラリ管理もできるポータブル実行スクリプトを作る

Python で「ちょっとしたスクリプト」を書きたい時、外部ライブラリを使おうとすると途端に面倒になる。pyproject.toml を作って uv add (もしくは uv sync) で依存をインストールして、できれば仮想環境も切って…という手順が必要だった。

これを Python ファイル1個だけで完結させる方法がある。PEP 723 (Inline script metadata, 2024 採択) と uv の組み合わせを使う。

完成形

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["jinja2>=3.1", "requests>=2.31"]
# ///
"""Render a Jinja2 template fetched from a URL."""
import sys
import requests
from jinja2 import Template

resp = requests.get(sys.argv[1])
print(Template(resp.text).render(name="world"))

これを保存して chmod +x script.py するだけで、

./script.py https://example.com/template.j2

で実行できる。pip installpython -m venv も不要。Python 自体が入っていなくても uv が自動でダウンロードする。

各行の解説

shebang 行

#!/usr/bin/env -S uv run --script
  • #! = Unix の shebang。カーネルがファイル先頭を見て「これで実行しろ」と解釈する
  • /usr/bin/env -S = env コマンドの -S オプション。これは「shebang の引数を空白で複数に分割していい」という意味。これを付けないと uv run --script1つの実行ファイル名として解釈されて command not found になる
    • macOS と Linux 4.0+ ならサポートされている
    • Ubuntu 18.04 以前など古い環境では動かない
  • uv run --script = uv に「このファイルを単体スクリプトモードで実行しろ」と指示

PEP 723 メタデータブロック

# /// script
# requires-python = ">=3.11"
# dependencies = ["jinja2>=3.1", "requests>=2.31"]
# ///

これが PEP 723 で標準化された inline script metadata の構文。

  • # /// script# /// で囲まれたブロック内の行は、各行の # プレフィックスを剥がしてから TOML としてパースされる
  • 普通のコメントとして書かれているので、PEP 723 非対応のツール (古い IDE, Linter 等) からはただのコメントとして無視される。互換性が壊れない
  • フィールドは pyproject.toml[project] テーブルと同じスキーマ
    • requires-python = 必要な Python バージョン (PEP 440 形式)
    • dependencies = 依存パッケージ (PEP 508 形式)

実行時に何が起きるか

./script.py を実行した時の流れ:

  1. カーネルが shebang を読んで /usr/bin/env -S uv run --script /path/to/script.py を起動する
  2. uv がファイル冒頭の # /// script ブロックを TOML としてパースする
  3. uv が ~/.cache/uv/このスクリプト専用の隔離仮想環境を作る (初回のみ、スクリプトのハッシュベースでキャッシュ)
  4. dependencies で指定したパッケージをその環境にインストールする (初回のみ)
  5. requires-python を満たす Python を探す。なければ uv が自動でダウンロードする
  6. その隔離環境で script.py を実行する

従来との比較

項目 従来 (uv プロジェクト方式) inline metadata + uv shebang
依存管理 pyproject.toml を別途作成 スクリプト1ファイルで完結
仮想環境 uv venv などで作成 uv が自動で作成・キャッシュ
インストール uv add jinja2 / uv sync 初回実行時に自動
Python バージョン管理 .python-version などで指定 uv が自動取得
実行 uv run python script.py ./script.py 直接
配布 プロジェクトディレクトリごと配る 1ファイルをメール添付・Gist でOK

数値で見るメリット

  • 初回実行: 〜3秒 (依存DLと Python 取得込み)
  • 2回目以降: 〜200ms (キャッシュヒット)
  • 配布性: スクリプト1ファイルで完結。Gist 1個・メール添付・Slack 貼り付けで動く
  • 環境汚染ゼロ: グローバル Python に何もインストールしない、隔離環境で動く

落とし穴

1. env -S は古い Linux で動かない

-S フラグは coreutils 8.30 (2018) からの機能。Ubuntu 18.04 以前、CentOS 7 などでは使えない。

回避策: shebang を書かずに、明示的に uv run --script script.py で起動する。

2. uv のインストールが前提

curl -LsSf https://astral.sh/uv/install.sh | sh

uv 自体は単一バイナリで配布されており、インストールも軽量 (Rust 製)。

3. キャッシュディレクトリの権限

~/.cache/uv/ への書き込み権限がないと失敗する。CI 環境やコンテナ内、サンドボックス環境などで問題になることがある。

環境変数で変更できる:

UV_CACHE_DIR=/tmp/uv-cache ./script.py

4. IDE が依存を認識しない

VSCode の Pylance などは uv が解決した仮想環境を見つけられないため、Import "jinja2" could not be resolved のような警告が出ることがある。

対処:

  • 警告を無視する (実害はない)
  • ファイル冒頭に # pyright: reportMissingImports=false を追加する
  • uv venv && source .venv/bin/activate && uv pip install jinja2 で IDE 用の環境を別途作る

5. PEP 723 対応ツールはまだ少ない

uv 以外だと Hatch, pdm が対応中。pip 単体ではこの機能を解釈できない。

ユースケース

向いている場面:

  • 配布したい一発ものスクリプト (社内ツール、Gist で共有するスニペット)
  • CI でしか動かさない補助スクリプト
  • ChatGPT / Claude に書いてもらった「あの処理」を1ファイルで実行したい時
  • ブログ記事に貼るサンプルコード
  • 依存ライブラリのある自作 CLI ツールを軽く配布したい時

向いていない場面:

  • 複数ファイルに分割される本格アプリケーション (普通に pyproject.toml を使う)
  • 起動レイテンシをとことん削りたい用途 (それでもキャッシュ後は 200ms 程度なので大半の用途は許容範囲)

まとめ

Python は長らく「shebang 1行で動かない」「依存があると環境構築が面倒」と言われてきた。

PEP 723 + uv の組み合わせで、Bash や Node.js が当たり前にやってきた「ファイル1つで完結する実行可能スクリプト」が Python でも自然にできるようになった。

ちょっとした自動化スクリプトを書く時は、もうこれが標準でいい。

参考

評価をお願いします
まだ評価がありません
著者は、アプリケーション開発会社 Cyberneura を運営しています。
開発相談をお待ちしています。

アーカイブ