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の置き場所を直すときは、失敗したときにどうなるかも一緒に見直したほうがいいです。だいたいセットで雑になっています。
明日やるなら、この順番で
curl -LsSf https://astral.sh/uv/install.sh | shでuvを入れる- スクリプトを置くフォルダで
~/.local/bin/uv venv .venv --python 3.13 - 必要な部品を
~/.local/bin/uv pip install --python .venv/bin/python ○○で入れる - 自分のスクリプトの中の
python3やpythonを、全部フルパスに書き換える - plistの
EnvironmentVariablesにPATHを書く - 時刻を待たずに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回で直したほうが早いです。


コメント