Open3

Macへuvをインストールして試行錯誤したメモ

segavvysegavvy

はじめに

Pythonの勉強で使ってるUbuntuとは別に、手元のMacでもPythonの実行環境が欲しくなったので、今回は以下のようにみなさん一押しのuvを入れてみました。

https://x.com/tetsu_1008/status/1908761898461651348

https://x.com/oshima_123/status/1908825014616654042

ちなみに最近のMacはデフォルトでPythonが入っておらず、使おうとすると「コマンドラインデベロッパツール」のインストールを勧めてくる親切設計です。

でも、今回はuvでPythonを入れたい(Pythonのバージョン管理もuvでできると聞いている)のでここはキャンセルして、先にuvを入れてみます。

uvのインストール

公式の手順で挑みます。

https://docs.astral.sh/uv/getting-started/installation/

Installation methods(インストール方法)

スタンドアロン インストーラーのmacOSの手順で進めます。

$ curl -LsSf https://astral.sh/uv/install.sh | sh
downloading uv 0.7.2 aarch64-apple-darwin
no checksums to verify
installing to /Users/ega/.local/bin
  uv
  uvx
everything's installed!

To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)

指示に従ってPATHを通すためにコマンドを実行します。使っているシェルはecho $SHELLで確認できますが、私はZshでした。

$ echo $SHELL
/bin/zsh

ちなみにsourceは今動いているシェルプロセス内で動かす指定ですね。環境変数の反映は同一プロセスで指定しないとすぐに反映されません。Windowsではコマンドプロンプトでバッチファイルを動かすと同一プロセスになりますがLinuxはデフォルトが逆なので注意です。

$ source $HOME/.local/bin/env

実行されるスクリプトの中身はこんな感じでした。PATHに~/.local/binがなければ先頭に追加する形みたいですね。

$ cat ~/.local/bin/env
#!/bin/sh
# add binaries to PATH if they aren't added yet
# affix colons on either side of $PATH to simplify matching
case ":${PATH}:" in
    *:"$HOME/.local/bin":*)
        ;;
    *)
        # Prepending path in case a system-installed binary needs to be overridden
        export PATH="$HOME/.local/bin:$PATH"
        ;;
esac

Upgrading uv(uvのアップグレード)

スタンドアロン インストーラーだとアップグレードも簡単だそうなので試してみましょう。

$ uv self update             
info: Checking for updates...
success: You're on the latest version of uv (v0.7.2)

最新版ですね。スタンドアロンのインストーラーが、常に最新に追従してくれているのかも。

Shell autocompletion(シェルの自動補完)

コマンド入力時に便利になりそうなので、シェルの自動補完も手順通りに設定してみます。

$ echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrc
$ echo 'eval "$(uvx --generate-shell-completion zsh)"' >> ~/.zshrc

そしてsource~/.zshrcを実行します。

$ source ~/.zshrc
(eval):4549: command not found: compdef
(eval):140: command not found: compdef

あれ?compdefってなんだろう。
ちょっと調べてみたところ、Zshの公式サイト曰く自動補完を使うためにはシェル関数compinitで初期化が必要とのこと。

https://zsh.sourceforge.io/Doc/Release/Completion-System.html#Use-of-compinit

To initialize the system, the function compinit should be in a directory mentioned in the fpath parameter, and should be autoloaded (‘autoload -U compinit’ is recommended), and then run simply as ‘compinit’. This will define a few utility functions, arrange for all the necessary shell functions to be autoloaded, and will then re-define all widgets that do completion to use the new system.
(Google翻訳)システムを初期化するには、関数compinit がfpathパラメータで指定されたディレクトリにあり、自動ロードされる必要があります (「autoload -U compinit」を推奨)。その後、単に「compinit」として実行されます。これにより、いくつかのユーティリティ関数が定義され、必要なすべてのシェル関数が自動ロードされるように調整され、新しいシステムを使用するために補完を行うすべてのウィジェットが再定義されます。

ふむふむ。そこで、~/.zshrcの最初(手前で追加した内容より前ならいいはず)にautoload -U compinitcompinitを追加しました。こんな感じです。

~/.zshrc
autoload -U compinit
compinit
. "$HOME/.local/bin/env"
eval "$(uv generate-shell-completion zsh)"
eval "$(uvx --generate-shell-completion zsh)"

そして、再びsource~/.zshrcを実行します。

$ source ~/.zshrc
zsh compinit: insecure directories, run compaudit for list.
Ignore insecure directories and continue [y] or abort compinit [n]? 

あれれ、今度はセキュアじゃないディレクトリがある?との警告が。再び公式サイトを確認すると、先ほどの続きにセキュリティチェックの解説もありました。

https://zsh.sourceforge.io/Doc/Release/Completion-System.html#Use-of-compinit

For security reasons compinit also checks if the completion system would use files not owned by root or by the current user, or files in directories that are world- or group-writable or that are not owned by root or by the current user. If such files or directories are found, compinit will ask if the completion system should really be used. To avoid these tests and make all files found be used without asking, use the option -u, and to make compinit silently ignore all insecure files and directories use the option -i. This security check is skipped entirely when the -C option is given, provided the dumpfile exists.
(Google翻訳)セキュリティ上の理由から、compinitは、補完システムが root または現在のユーザーが所有していないファイル、またはワールドまたはグループが書き込み可能なディレクトリ内、あるいは root または現在のユーザーが所有していないディレクトリ内のファイルを使用するかどうかも確認します。このようなファイルまたはディレクトリが見つかった場合、 compinit は補完システムを本当に使用すべきかどうかを確認します。これらのテストを回避し、見つかったすべてのファイルを確認なしで使用するには、オプション-uを使用します。また、安全でないファイルとディレクトリをすべてcompinitが無視するようにするには、オプション-iを使用します。ダンプファイルが存在する 場合、 -Cオプションが指定されると、このセキュリティチェックは完全にスキップされます。

なるほど、ディレクトリの権限がよくない模様です。エラーメッセージに従いcompauditすると対象ディレクトリを教えてくれました。

$ compaudit
There are insecure directories:
/usr/local/share/zsh/site-functions
/usr/local/share/zsh

2つあるので確認してみます。あ、lsでディレクトリ調べる時は-dが必要と。Linux初心者の私はこんなところでも時間が溶けていきます😅

$ ls -ld /usr/local/share/zsh/site-functions
drwxrwxr-x  4 ega  admin  128 10  4  2020 /usr/local/share/zsh/site-functions

なるほど、所有者は自身の「ega」なのでいいのですが、グループに対する「w」(書き込み可能)が引っかかっている模様です。chmod g-wで外してみましょう。

$ chmod g-w /usr/local/share/zsh/site-functions
$ ls -ld /usr/local/share/zsh/site-functions   
drwxr-xr-x  4 ega  admin  128 10  4  2020 /usr/local/share/zsh/site-functions

これでグループの「w」が取れました!
もう1つのディレクトリも確認します。

$ ls -ld /usr/local/share/zsh
drwxrwxr-x  3 ega  admin  96 10  4  2020 /usr/local/share/zsh

こちらも同様なのでグループの「w」を外します。

$ ls -ld /usr/local/share/zsh   
drwxr-xr-x  3 ega  admin  96 10  4  2020 /usr/local/share/zsh

これで再びcompauditすると検出されなくなりました!
再び~/.zshrcを実行します。

$ compaudit                     
$ source ~/.zshrc

お、エラーが出なくなりました!
これで、途中まで入力してTabを押すと補完してくれるようになりました!!

これでuvのインストールは完了です!

Pythonのインストール

続いて、公式の手順でPythonの最新版をインストールしてみます。

Installing Python(Pythonのインストール)

$ uv python install
warning: Failed to patch the install name of the dynamic library for /Users/ega/.local/share/uv/python/cpython-3.13.3-macos-aarch64-none/bin/python3.13. This may cause issues when building Python native extensions.
Installed Python 3.13.3 in 1.97s
 + cpython-3.13.3-macos-aarch64-none

あぅ、なんか警告が。あと、再び「コマンドラインデベロッパツール」のインストールを勧めるダイアログが出てきました。今回は「install_name_tool」を実行するために必要と言っています。

ちょっと調べてみたら、uvはインストール時にこのinstall_name_toolを使うそうで、コマンドラインデベロッパツールは入れておかないといけなかった模様です。うぅ、初めからコマンドラインデベロッパツールを入れておけばよかった😢

とりあえず、警告付きでインストールされたPythonは怖いので一度削除します。

$ uv python uninstall cpython-3.13.3-macos-aarch64-none
Searching for Python versions matching: cpython-3.13.3-macos-aarch64-none
Uninstalled Python 3.13.3 in 104ms
 - cpython-3.13.3-macos-aarch64-none

なお、ここではバージョン指定の仕方がよくわからずインストール時に表示されたcpython-3.13.3-macos-aarch64-noneを指定してアンインストールしましたが、公式サイトのRequesting a versionに解説があり、バージョン番号だけでも指定できるそうです。

コマンドラインデベロッパツールのインストール

続いて、先ほどのコマンドラインデベロッパツールのダイアログで「インストール」します。ここはいつものMacのインストーラーなので簡単ですが、手元のM2 Mac miniでは10分ほどかかりました。

終わったら再び挑戦です。

Pythonのインストール再び

$ uv python install
Installed Python 3.13.3 in 2.52s
 + cpython-3.13.3-macos-aarch64-none

お、うまくいきました!

Viewing available Python versions(利用可能なPythonのバージョンを表示する)

使えるようになったPythonのバージョンを確認してみます。

$ uv python list --only-installed
cpython-3.13.3-macos-aarch64-none    /Users/ega/.local/share/uv/python/cpython-3.13.3-macos-aarch64-none/bin/python3.13
cpython-3.9.6-macos-aarch64-none     /usr/bin/python3

あれ、2つ出てきた。後者はuvディレクトリ下にいないので、どうやら先ほどのコマンドラインデベロッパツールで入ってしまったものみたいです。

管理対象外のものだけに絞ってみます。

https://docs.astral.sh/uv/concepts/python-versions/#requiring-or-disabling-managed-python-versions

$ uv python list --no-managed-python 
cpython-3.9.6-macos-aarch64-none    /usr/bin/python3

やっぱりそうですね。3.9.6はuvの管轄外になっています。どうせこうなるのなら、初めからコマンドラインデベロッパツールを入れておけばよかった(2回目)。

Creating a new project(新しいプロジェクトの作成)

公式の手順でプロジェクトを作ってみます。

$ uv init hello-world
Initialized project `hello-world` at `/Users/ega/projects/hello-world`
$ cd hello-world 
$ uv run main.py
Using CPython 3.13.3
Creating virtual environment at: .venv
Hello from hello-world!

お、Venvの環境を自動で作ってくれた!activateもしてくれてるのかも?

$ echo $VIRTUAL_ENV

$ python3 --version
Python 3.9.6

どうやら、今のシェル内で切り替わる訳ではなさそう。

$ uv run python3 --version
Python 3.13.3

なるほど、あくまでもuvコマンド経由での世界ということですね。
とりあえずPythonのインストールまではできたので、今日はここまで。

segavvysegavvy

前回の続きで、公式の説明を見ながらプロジェクトでの使い方を確認します。

https://docs.astral.sh/uv/guides/projects/

Project structure(プロジェクト構造)

前回のuv initでファイルが一式できています。
別で勉強を進めていたubuntuではpyenv+venv+pipを使っていましたが、それがuvだけで済む形です。

Managing dependencies(依存関係の管理)

uv add {パッケージ}すると、インストールしてpyproject.tomluv.lockも更新してくれます。

削除はuv remove {パッケージ}ですね。

また、uv lock --upgrade-package {パッケージ}すると他のパッケージはそのままキープしつつ、それらと互換のある一番新しいバージョンに更新してくれるそうです。便利!

Running commands(コマンドの実行)

uv run {スクリプトやコマンド}で実行すれば、環境をpyproject.tomluv.lockの内容に合わせてから実行してくれるそうです。つまり、pyproject.tomluv.lockの情報に現状があってなければパッケージのインストールなどを自動的に実行してくれるわけですね。

なお、以下の実行例で--という謎のオプション?があって調べてみたら、これは「以降はオプションはない」ことを示す慣習だそうです。つまり、以降はuvではなくflaskのオプションとして解釈される訳ですね。

uv add flask
uv run -- flask run -p 3000

uv runは他にもいろいろなオプションがあり、以下のページで解説されています。

https://docs.astral.sh/uv/guides/scripts/

また、uv syncで、pyproject.tomluv.lockの内容に合わせてPythonのインストールやパッケージのインストールなどをやってくれます。uv runを使っていれば不要みたいですが、これまでどおりコマンドを実行したい場合は、uv sync後にvenvのアクティベートをすればOKです。

uv sync
source .venv/bin/activate
flask run -p 3000
python example.py

Building distributions(ディストリビューションの構築)

これはパッケージを配布する際の機能ですね。今は使わないのでスルーします。


これで、ざっくり必要最低限のことがわかりました。
なお、以下にあるスクリプトの実行やツールの使い方などは読み飛ばしているので、また使う時に戻ってこようと思います。

https://docs.astral.sh/uv/guides/scripts/

https://docs.astral.sh/uv/guides/tools/

segavvysegavvy

uv runで実行時の引数の解釈について

前回、コマンドラインの引数で--というのが出てきて「以降はオプションはない」ことを示す慣習とのことでした。

uv run -- flask run -p 3000

でも、今勉強中の教材で、

uv run python -m app.hoge --user "fugo"

みたいなのが出てきて、--がないので-m以降もuv runのオプションとして解釈されるのかと思ったのですが、uv runにオプションが見つからず、調べてみたら誤解していたことがわかりました。

https://docs.astral.sh/uv/reference/cli/#uv-run

Arguments following the command (or script) are not interpreted as arguments to uv. All options to uv must be provided before the command, e.g., uv run --verbose foo. A -- can be used to separate the command from uv options for clarity, e.g., uv run --python 3.12 -- python.
(Google翻訳)コマンド(またはスクリプト)に続く引数は、uvの引数として解釈されません。uvのオプションはすべてコマンドの前に指定する必要があります(例: )uv run --verbose foo。--コマンドとuvのオプションを区別するために、 を使用できます(例:uv run --python 3.12 -- python)。

Google翻訳だと--などが変な位置になってしまいますが、もともと--がなくてもuv run以降はuvの引数にはならず、すべてその後に続くコマンドやスクリプトへの引数になるとのことです。ただ、わかりやすさのために--を入れてもいいですよとのことで、前回の--はわかりやすさのために入っていたことがわかりました。

今回の場合は以下の感じですね。

uv run python -m app.hoge --user "fugo"
引数 意味
python uv runの実行対象。以降は--がなくてもuvの引数とは解釈されない。
-m pythonのオプションで、指定されたモジュール(app.hoge)をスクリプトとして実行する。
app.hoge スクリプトとして実行するモジュール名。
--user "fugo" モジュール名より後ろはそのままモジュールに渡る引数。

わかりやすさの配慮の有無で混乱してわかりにくくなっていた訳ですね😅