5 ポイント 投稿者 GN⁺ 2025-03-31 | 1件のコメント | WhatsAppで共有
  • 小さなAPIテストスクリプトでも依存関係のために実行環境をそろえる必要がありますが、uv実行shebangを使えば、インストール手順なしですぐ実行できる形にできます
  • 例の jam_users.py は、Go APIの /users エンドポイントをテストするために httpxIPythonloguru を使い、ユーザーの削除・作成後にREPLへ入ります
  • 従来の方法では、システムPythonにパッケージをグローバルインストールするか、仮想環境を自分で用意する必要があり、スクリプトの共有や再実行が面倒でした
  • # /// script ヘッダーで dependencies = ["ipython", "httpx", "loguru"] を宣言し、uv run jam_users.py を実行すると、uvが隔離環境と依存関係のインストールを処理します
  • #!/usr/bin/env -S uv run --script というshebangと実行権限を追加すれば、Unixシステムで uvだけがインストールされた状態 でも ./jam_users.py のように実行できます

スクリプト内で依存関係を宣言する

  • 例の jam_users.py は、ローカルAPI http://localhost:4000/v1/users を対象にテスト用ユーザーデータを準備します
    • httpx でAPIリクエストを送信します
    • IPython REPLに入り、レスポンスを確認しながらテストを続けます
    • loguru で削除と作成のログを残します
  • 基本スクリプトは、ユーザー一覧を初期化した後、テストデータを再投入する流れです
    • GET /v1/users で既存ユーザー一覧を取得します
    • 各ユーザーに対して DELETE /v1/users/{id} を呼び出します
    • 用意したユーザー一覧を POST /v1/users にJSONで送信します
    • その後 IPython.embed() でREPLを起動します
  • python jam_users.py で実行するには、httpxIPythonloguru が実行環境にすでにインストールされている必要があります
  • システムPythonにグローバルインストールしたり、仮想環境を別途作ったりする方法もありますが、どちらも実行前の準備が必要です
    • グローバルインストールは、システムPythonをパッケージで散らかしてしまう可能性があります
    • 仮想環境方式では、作成、アクティベート、インストール、実行の手順を自分で管理する必要があります
    • どちらの方式でも、必要なパッケージと互換性のあるシステムPythonが必要です

uvで直接実行できるスクリプトを作る

  • uv# /// script タグをスクリプトの先頭に入れると、ファイル内で依存関係を宣言できます
# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • このヘッダーがあると、uv run jam_users.py で実行できます
    • uvがスクリプト用の隔離された仮想環境を作成します
    • 必要な依存関係をダウンロードしてインストールします
    • その仮想環境のコンテキストでスクリプトを実行します
  • 通常のPython shebangである #!/usr/bin/env python では、Pythonが # /// script コメントを無視するため、uvのスクリプトヘッダーを活用できません
  • shebangにuv呼び出しを直接入れると、スクリプトを実行ファイルのように扱えます
#!/usr/bin/env -S uv run --script

# /// script

# dependencies = ["ipython", "httpx", "loguru"]

# ///
  • env-S フラグは、後続の文字列を個別の引数に分割して env に渡すためのものです
  • chmod +x jam_users.py で実行権限を付与した後は、次のように直接実行できます
./jam_users.py
  • この方法では、Unixシステムに uvがインストールされていれば、別途依存関係をインストールしたり仮想環境を管理したりせずにスクリプトを実行できます
  • 複雑なPythonスクリプトを他のユーザーに渡すとき、実行前のシステム準備手順を長々と案内する負担を減らせます

1件のコメント

 
GN⁺ 2025-03-31
Hacker Newsの意見
  • UVそのものではなく全般的に、コメントでコード実行を制御するやり方が気になる
    リンター指示や開発者メモにコメントを使うのはよいが、設定や実行関連データなら UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } } のような形のほうがずっとよいと思う
    この方式は有効なPython構文で、任意のコメントをパースする代わりに標準のデータ構造を使うため生成と検証がしやすく、何よりコメントをすべて削除してもコードは同じように実行されるべきだという原則を守れる

    • 同意するが、さらに一歩進めたい
      提案された方式も実行時には何もしないマジック定数なので、静的解析でしかパースされず、uvが読む代わりに別のツールが未使用コードと見なして削除してしまう可能性もある
      いっそ import uv の後に uv.exec(dependencies=["clown"], python=">=3.10") のように、uvに何をするかを直接呼び出させればよい
      初回実行は、この仮想的な uv パッケージを見つけられる任意のPythonランタイムで行い、uv パッケージが仮想環境とPythonランタイムを用意した後、環境変数のようなフラグを付けて re-exec(3) すればよい
      2回目のランタイムでは、フラグを検知した uv.exec が何もしなければよい
    • これはuvが発明したものではなく、他のツールと同様に標準のPEP 723を使っているもの
      https://peps.python.org/pep-0723/
    • 妥当ではあるが、命令型コードをパースして評価するのは、最小権限の原則に沿って宣言的データとして置いておくよりもはるかに難しく、柔軟性も低いと思う
      また、shebang行自体も実際にはコメントとして動作する
      45年もの間あまりに深く根付いたため、それがシェルのコメントだという事実を人々があまり意識していないだけ
    • 「コメントを削除してもコードが同じように実行されるべき」という条件は今でも正しい
      同じ依存関係をインストールしていれば、コードは同じように実行される
      コード自体の意味を変えるというより、コードが実行される環境を変えているのであり、その意味ではシェルスクリプト先頭の #!/bin/bash コメントと変わらない
    • 完全に同意するし、こうした形が最終的に標準化されることを望む
      ただしuvは依存関係を知るためにコードを実行したくない可能性が高いので、Python構文のごく限定されたサブセットである必要があるだろう
      そもそもこういうものが必要だという事実は、言語の弱点を示している
      import 文自体が依存関係に関するすべての情報を伝えられるべきだ
  • ここ数か月、HNではこの話題が非常に頻繁に出ており、最近の例は次のとおり
    https://news.ycombinator.com/item?id=43500124
    https://news.ycombinator.com/item?id=42463975
    uvは好きだが、self-containedという表現には2つの点で同意しにくい
    第一に、スクリプトを実行するにはuvがすでにインストールされている必要がある
    シェルスクリプトでuvのインストール有無を確認し、なければcurlpipeでインストールするようにはできるが、ボイラープレートがかなり増えるし、curlpipe方式自体も好ましくない
    第二に、ホームディレクトリのどこかに仮想環境を自動生成するのは、本当のself-containedではない
    一度だけ実行してスクリプトを削除しても、その仮想環境は残って容量を占有し、uvのドキュメントでこうした一時的な仮想環境を自動的に整理するという保証は見つけられなかった

    • uvが必要で、なければ手動でインストールするか curl | sh を使わなければならないという不満は妥当
      ただしパッケージマネージャーがuvをリポジトリに含め始めれば、次第に問題ではなくなっていくだろう
      例えばuvはすでにAlpine LinuxとHomebrewで提供されている: https://repology.org/project/uv/versions
      また、インラインスクリプトメタデータはPython標準である
      システムにuvがなく、パッケージ化もされていないが、スクリプトに合うPythonバージョンがあるなら、pipxで実行できる: https://pipx.pypa.io/stable/examples/#pipx-run-examples
      pipxははるかに広くパッケージ化されている: https://repology.org/project/pipx/versions
    • こうした問題を解くために、docker+uv shebangを無理やり組み合わせてみたところ、ある程度動作する
      任意の開発者マシンではuvよりDockerのほうが一般的かもしれず、元記事も会社のプロジェクトだというので、それらしく思えた
      ただし現状ではキャッシュをまったくしていないため、実行するたびにダウンロードが発生する点がぎこちなく、ボリュームで直せそうではある
      こういう形: https://hugojosefson.github.io/docker-shebang/#python
    • 普通、コンピューターでプログラムを実行するには何かを先にインストールする必要があるので、uvのインストール自体はそれほど悪くないように思う
      それでも、実行時にインターネットから正体不明のものをダウンロードするならself-containedとは呼びにくく、本当に完全なself-containedはAppImageに近い
    • 100%同意
      py2exeのようなものを使えば、self-containedな「Pythonスクリプト」を作れる
      開発者には多くの問題が生じるが、ユーザーにとっての問題は最小化される
    • 些細な補足として、uvのパッケージ重複排除のおかげで、固有の依存関係がない限りvirtualenvは容量を占有しない
  • Nix でも同じ方式を使っており、shebang 行は次のような形になる
    #! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"
    こうすればシステムに必要なのは Nix だけで、Python がインストールされている必要すらない

    • その通りではあるが、まだ nixpkgs でパッケージ化されていない PyPI パッケージが多いので、uv ほど汎用的なアプローチではない
    • 原文でもまさにそうであるように、uv は必要なときに Python をその場でインストールしてくれる
    • 同じ手法はどんな言語にも適用できる
      いちばん分かりやすい例は、すべての依存関係を指定した bash で、Nix shebang を使って高速な単一ファイルの Rust スクリプトを作ったこともある
      https://nixos.wiki/wiki/Nix-shell_shebang
    • Nix がインストールされている必要があるという条件は、uv が必要という条件よりはるかに強い要件だ
    • nix-shell の代わりに flake ベースのコマンドである nix shell で同じことをする方法が気になる
  • 他のコメントにもあるように、「self-contained」という主張は uv がインストールされていることに依存している
    本当に self-contained な Python スクリプトが欲しいなら、Nuitka コンパイラを見てみる価値がある
    gRPC サービスの本番環境で問題なく使っていて、nuitka --onefile run.py を実行するだけで済む
    コンパイラなので、PyInstaller でまとめた元の Python プログラムより、生成されるバイナリが速いこともある
    作者の GitHub ページには「可能な限り最高の Python Compiler を作るために、年老いて死ぬまで試み続けることが私の人生の使命だ」という文言がある
    https://nuitka.net/
    https://github.com/kayhayen

  • このパターンは本当に気に入っているが、残念ながら LSP とはうまく動かせなかった
    Helix で pyright を使っていて、エディタを uv run hx script.py で起動しても駄目だった
    uv run --with whatever-it-is-i-need hx script.py のようにすることはできるが、だんだん重複が増えていく

    • 自作の見栄えの悪い uve スクリプトを使っている
      $ cat ~/.local/bin/uve
      #!/bin/bash
      temp=$(mktemp)
      uv export --script $1 --no-hashes > $temp
      uv run --with-requirements $temp vim $1
      unlink $temp
      エディタが近いうちに uv python find --script をサポートしてくれるといい
  • かなり便利そうだ
    Python ベースのプロジェクトを長期的に配布する場合、uv はより安全な選択肢なのか気になる
    約5年前に依存関係管理に Anaconda を使っていたが、後にルールが変わり、従業員200人以上の組織顧客は Anaconda を無料で使えなくなり、商用ライセンス料を支払う必要が出たという rug pull を思い出している

    • uv は MIT または Apache-2.0 ライセンスだ
      開発を中止したり、今後の作業を別ライセンスのフォークに移したりすることはできるが、過去のライセンスを遡って変更することはできないので、現在存在しているものはオープンソースであることが保証されている
      本当に心配なら、フォークを作って同期し続ければよい
      実質的に他の OSS プロジェクトにもほぼ同じように当てはまる話なので、あまり心配しない
      私の知る限り、conda はオープンソースだったことはなく、バイナリを配布してきた
      https://github.com/astral-sh/uv?tab=readme-ov-file#license
    • Anaconda の rug pull はリポジトリ側にかかったものだと思う
      conda-forge のパッケージは今でも無料で使える
      uv は単に PyPI を使っているだけなので、問題が起きたら uv から pip や Poetry などに切り替えればよく、パッケージは引き続き同じ場所から取得することになる
    • プロジェクトにコントリビューターライセンス契約(CLA)があり、貢献の著作権をプロジェクト所有者に譲渡させるなら、再ライセンスが可能だと理解している
      そういう所有者は、結局思いつく限り最悪の金持ちに買収されるものだ
      uv のコントリビューションガイドと issue をざっと見たが CLA は見当たらず、PyTorch ではコントリビューションガイドの一番上に CLA があった
      それでも Anaconda の最後の FOSS 版にはコミュニティフォークがあるべきだった
      Redis ではそうなったし、Redis も CLA を使っている: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
      絶対に CLA に署名してはいけない
      むしろ copyleft プロジェクトにだけ貢献しようと思う
      私たちは無償で働くにはあまりにも高い給料をもらっている
  • 小さなユーティリティには、コンテナ化の代わりになる良い パッケージング代替案に見える
    あとは同僚全員に uv をインストールするよう説得しなければならない

    • uv は驚くほど速いので助けになるだろう
    • 私たちにとっては、SCA 脆弱性スキャナーがまだ uv と連携しないことが障害になっている
  • Ruby の bundler/inline に似ているように見える
    Python にも似たものができてうれしいし、実際とても便利だ
    https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html

  • Windowsでこれを動かせた人がいるのか気になる
    作業中のゲームMod用ツールでこのトリックを使いたかったが、shebangのトリックを動かせなかった

  • このユースケースのおかげでuvが好きになったが、公式で非常に有用なPEPが公式のPythonツールでサポートされていないのは Zen of Python に反していると感じる
    自分の見方では、Pythonが初めて「batteries included」ではなくなった瞬間だ
    今ではシステムにPythonの依存関係マネージャーも2つ入ることになった
    Pythonの依存関係管理について言いたいことが多いのは分かっているが、ここ数年はプロジェクトにrequirements.txtさえあれば、標準のpip+venvでしのいできた

    • Pythonのビルド仕様では、そういう流れがいくらかあった
      pyproject.tomltomllibライブラリより先に出ていたと記憶している
      そのため数バージョンの間、Pythonが標準では読めない言語でモジュールを指定しなければならなかった
      使われないメタデータを含める標準的な方法があるより、さらに悪い状況だ
      そもそもだからこそメタデータなのであって、そうでなければ単にPython構文だったはずだ