XcodeなしのMacでPython自動化を動かすまで|uvを選んだ理由

XcodeなしのMacでPython自動化を動かすまで|uvを選んだ理由 自動化のはじめ方

Pythonの入門記事は、たいてい python3 -m venv .venv から始まります。うちのMacでは、この1行が動きませんでした。

結論を先に書きます。macOSに最初から見えている /usr/bin/python3 は、Xcode Command Line Tools を入れるまで実体がありません。そして、Pythonのスクリプトを毎日動かしたいだけなら、開発一式を入れなくても構いません。うちは uv という道具でホームフォルダの中にPython 3.13を1本だけ置き、launchdからはそのフルパスを名指しで呼ぶ形にしました。この記事は、その手順と、launchdに毎回同じPythonを使わせるための決めごとです。

「Macには最初からPythonが入っている」は半分だけ本当

/usr/bin/python3 というファイル自体は、どのMacにもあります。だから「入っている」と書いてある記事は間違いではありません。ただしそれは入り口だけで、中身はXcode Command Line Toolsという開発者向けの一式に含まれています。入れていないMacでこれを呼ぶと、Pythonは起動しません。

ここがやっかいなのは、ファイルが存在するせいで「パスが違うのかな」と別の方向を探してしまうことです。私は最初、自分の書き方が悪いのだと思ってしばらく回り道をしました。実際は、呼び先が空だっただけです。

うちのMacはいまもCommand Line Toolsを入れていません。入れる手もありましたが、選ばなかった理由は3つあります。

  • 目的に対して大がかりすぎる。やりたいのはExcelを読んで集計するスクリプトを毎日動かすことで、アプリ開発ではありません
  • システム側のPythonは、こちらの都合で選べない。macOSの更新で入れ替わる可能性があり、そのタイミングをこちらは決められません
  • 消したくなったときに戻しづらい。システムに入れたものは、消す作業自体がまた調べ物になります

uvにした理由は「全部ホームフォルダの中で終わる」こと

uvは、Pythonそのものと、そのPythonが使う部品(ライブラリ)をまとめて用意してくれる道具です。決め手は速さではなく、置き場所がホームフォルダの中で完結することでした。

うちのMacで、実際にPython本体が置かれているのはここです。

~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/

/usr/ の下ではなく、自分のホームフォルダの中です。つまりシステムには一切触っていません。やめたくなったらこのフォルダを消せば元通りで、macOSの更新で勝手に入れ替わることもありません。非エンジニアが最初に入れる道具としては、この「取り返しがつく」という性質がいちばん大事だと思っています。

実際に入れた3行

まずuv本体を入れます。ターミナルに1行です。

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

これで ~/.local/bin/uv ができます。次に、スクリプトを置くフォルダごとに「作業場所」を作ります。うちの社長AIのフォルダを例にすると、こうです。

cd ~/scripts/ai/line && ~/.local/bin/uv venv .venv --python 3.13

そこへ必要な部品を入れます。

cd ~/scripts/ai/line && ~/.local/bin/uv pip install --python .venv/bin/python anthropic

uv をフルパス(~/.local/bin/uv)で書いているのはわざとです。~/.local/bin にパスが通っていないターミナルでも、これならそのまま動きます。

つまずいた点:uvで作った作業場所には pip がいない

Pythonの記事によく出てくる pip install ○○ は、うちでは動きません。uvが作った .venv/bin の中に pip というファイルが無いからです。入っていないものは呼べません。

代わりに、「どのPythonに入れるか」を明示して uv に頼みます。

~/.local/bin/uv pip install --python ~/scripts/keiri/.venv/bin/python openpyxl

--python でPythonを名指しするので、いまどのフォルダにいても、どの作業場所に入れるかを間違えません。うちの経理用の作業場所には、この形でExcelを読む openpyxl、Googleのスプレッドシートを取りに行く部品、通信用の requests などが入っています。

Pythonは1本、作業場所は用途ごとに分ける

いまうちには作業場所が3つあります。経理(~/scripts/keiri/.venv)、社長AI(~/scripts/ai/line/.venv)、希望シフト受付(~/scripts/ai/kibou/.venv)です。

3つとも、中の pyvenv.cfg というファイルを開くと同じことが書いてあります。

cat ~/scripts/keiri/.venv/pyvenv.cfg
home = /Users/あなたのユーザー名/.local/share/uv/python/cpython-3.13-macos-aarch64-none/bin
implementation = CPython
uv = 0.12.5
version_info = 3.13

home の行が「この作業場所が、どのPython本体を使っているか」です。3つとも同じ行になっている、つまりPython本体は1本で、部品置き場だけが3つに分かれている状態です。これが狙いどおりの形です。片方の都合で部品を入れ替えても、もう片方は巻き添えになりません。

いま自分の環境がどうなっているか、この1行で確かめられます。version_info がバージョン、uv の行が作ったときのuvのバージョンです。

launchdに、確実に同じPythonを使わせる

ここからが本題です。手で叩くときは cd してから .venv/bin/python と打てば済みますが、launchd(Macに毎日決まった時刻に動かしてもらう仕組み)にはそれが通じません。うちは3つ決めごとを置いています。

① スクリプトの中で、Pythonをフルパスで名指しする

いちばん効きます。うちのメディア側は設定ファイルに1行だけ書いて、全部そこを見ています。

# Python(このMacは /usr/bin/python3 が動かないので venv のものを使う)
MEDIA_PY="${MEDIA_PY:-$HOME/scripts/keiri/.venv/bin/python}"

毎朝の予定を知らせるスクリプトは、もっと単純に1行です。

exec "$HOME/scripts/keiri/.venv/bin/python" "$HOME/scripts/asa/朝の予定.py" "$@"

source .venv/bin/activate は使っていません。あれは「そのターミナルの中でだけ python の行き先を変える」仕掛けで、launchdから呼ばれる短い処理には向きません。activateを忘れたら別のPythonが動くという作りにするより、最初からフルパスで名指しするほうが事故りません。

② plistにPATHを書いておく

launchdは、ふだんターミナルを開いたときの設定を読み込みません。だから ~/.local/bin にあるはずの道具が見つからず落ちます。うちのplistには全部これを書いています。

<key>EnvironmentVariables</key>
<dict>
    <key>PATH</key>
    <string>/Users/あなたのユーザー名/.local/node/bin:/Users/あなたのユーザー名/.local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>

①でPythonを名指ししていても、②は要ります。スクリプトの中でuvや他の道具を呼ぶことがあるからです。

③ 呼ぶ前に「本当にそこにあるか」を確かめる

毎日の自動化がちゃんと動いたかを見張るスクリプトでは、Pythonを呼ぶ前に存在を確認しています。

NIPPOU_PY="$HOME/scripts/keiri/.venv/bin/python"
if [ -x "$NIPPOU_PY" ] && [ -f "$NIPPOU_CHK" ]; then
  ...
else
  GYOU+=("❌ 10:00 日報の取り込み … 取込状況.py が見つかりません")
fi

[ -x ] は「そのファイルがあって、実行できるか」の確認です。作業場所を作り直したときにここが空振りすると、処理は静かに何もしないまま「正常終了」します。先に確かめて、無ければはっきり失敗と言わせるほうが安全です。

実際の書き換え:before と after

うちの月末着地予想を出すスクリプトは、最初こう書いてありました。

# 2026年8月18日時点
/usr/bin/python3 predict.py > "$OUT" 2>&1

いまはこうです。

if ! "$(dirname "$0")/.venv/bin/python" predict.py > "$TMP" 2>&1; then
  chuushi "着地予想の計算に失敗したため中止しました"
fi

変えたのはPythonの指定だけではありません。失敗したかどうかを見て、失敗なら止まる形に直しています。最初の版は結果をそのままファイルに流し込んでいたので、Pythonが動かなくてもエラーの文章が「レポート」として保存され、Slackにも流れていました。Pythonの置き場所を直すときは、失敗したときにどうなるかも一緒に見直したほうがいいです。だいたいセットで雑になっています。

明日やるなら、この順番で

  1. curl -LsSf https://astral.sh/uv/install.sh | sh でuvを入れる
  2. スクリプトを置くフォルダで ~/.local/bin/uv venv .venv --python 3.13
  3. 必要な部品を ~/.local/bin/uv pip install --python .venv/bin/python ○○ で入れる
  4. 自分のスクリプトの中の python3 や python を、全部フルパスに書き換える
  5. plistの EnvironmentVariables にPATHを書く
  6. 時刻を待たずに1回手で動かし、そのあと本番の時刻でもう1回、ログを見て確かめる

4番は面倒ですが、ここを飛ばすと「手では動くのにlaunchdでは動かない」に必ず戻ってきます。

まとめ

  • /usr/bin/python3 はファイルはあっても実体がないことがある。Xcode Command Line Tools を入れていないMacでは動かない
  • Pythonを動かしたいだけなら開発一式は要らない。uvならホームフォルダの中で完結し、やめたければフォルダごと消せる
  • uvが作った作業場所に pip は入っていない。uv pip install --python …/.venv/bin/python の形で入れる
  • Python本体は1本、作業場所は用途ごと。いまどれを使っているかは pyvenv.cfg の home 行で分かる
  • launchdには①フルパスで名指し ②plistにPATH ③呼ぶ前に [ -x ] で存在確認の3点セット
  • activate に頼らない。忘れたら別のPythonが動く作りにしない
  • Pythonの指定を直すときは、失敗したときに止まるかどうかも一緒に直す

フルパスに書き換える作業をこれからやるなら、Macでは動くのにlaunchdではファイルが見つからない|相対パスの落とし穴 を先に読んでください。Pythonの場所と同じ理由で、読み込むファイルの場所でも同じことが起きます。まとめて1回で直したほうが早いです。

コメント

タイトルとURLをコピーしました