本文作者是 AI 工程的初學者,正透過《AI Engineering from Scratch》課程自學,並全程搭配 AI 輔助:一步步照著課程開發者的進度動手執行,卡關時就向 AI 提問、請它引導。這篇文章,是把作者與 AI 互動的完整過程與學習歷程,先交由 AI 記錄、統整成初稿,再由作者親自逐段審視、修改、優化而成。AI 負責整理,最終判斷與文字由作者把關。
寫下它有兩個用意:替後來的讀者鋪一條能照著走的路徑,也幫作者自己複習、把學過的東西沉澱下來。
這一課跟 Lesson 1 有重疊
第一課 Dev Environment 的時候,我已經建過 .venv、用 uv pip install 裝過套件。這一課又帶我做了一次類似的事情。
差別在於:第一課是「先讓你能跑起來」,這一課才是「回頭教你為什麼要這樣做」。概念的部分之前學過一輪,但動手的部分只做了一半。這次決定全部重來細跑一遍,把每個指令的意思搞清楚。
這一課導覽框
- 課名:Phase 0 - Lesson 6 — Python Environments
- 目標:理解 venv 隔離的原理、學會
uv init和uv add、搞懂 pyproject.toml 和 lockfile 的分工- 時間:課程標約 30 分鐘,我邊理解邊動手大概花了 50 分鐘
- Mac 使用者:CUDA 相關的內容跳過,Mac 走 MPS 不走 CUDA
- 完課驗證:跑
env_setup.sh全部 PASS、理解 pyproject.toml 和 uv.lock 的角色
這一課要解決的問題
課程開頭就說:
Dependency hell is real. Virtual environments are the cure.
依賴地獄是真的。虛擬環境是解藥。
什麼是依賴地獄?你的 A 專案需要 PyTorch 2.4,B 專案需要 PyTorch 2.1。兩個版本不能同時存在於同一個 Python 環境裡,你裝了 2.4,B 就壞了;裝了 2.1,A 就壞了。在 AI/ML 的世界裡,這種事會不斷發生,因為 PyTorch、JAX、TensorFlow 各自綁定不同版本的 CUDA。
解法就是:每個專案給它自己獨立的環境,各裝各的套件,互不干擾。
課程給的三種工具
課程列了三種建 venv 的方式:
| 工具 | 特色 | 什麼時候用 |
|---|---|---|
uv | 速度最快(比 pip 快 10-100 倍),課程推薦 | 大部分情況 |
python3 -m venv | Python 內建,不用額外裝 | 沒有 uv 的環境 |
conda | 能管 Python 以外的東西(CUDA toolkit、C 函式庫) | 需要特定 CUDA 版本,或在共用伺服器上 |
我用的是 uv,之前 Lesson 1 就裝好了。
conda 有一條規則:如果你用 conda 建了環境,裡面的套件就全部用 conda 裝,不要混 pip 進去。混了會讓 conda 的依賴追蹤亂掉。
動手:用 uv init 從零建一個專案
課程有兩種動手方式。第一種是 uv venv + uv pip install,在既有的資料夾裡建環境裝套件,我在 Lesson 1 已經做過。第二種是 uv init,從頭建一個全新的專案。
我在課程的 06-python-environments/ 底下建了一個練習用的資料夾:
cd ~/Desktop/Learning_AI/ai-engineering-from-scratch/phases/00-setup-and-tooling/06-python-environments
uv init my-ai-project
cd my-ai-project
uv init 幫我生了三個檔案:
| 檔案 | 是什麼 |
|---|---|
pyproject.toml | 專案的設定檔,記錄名稱、Python 版本需求、需要哪些套件 |
.python-version | 記一行 Python 版本號,讓工具知道該用哪版 |
main.py | 一個 hello world 範例 |
注意:uv init 這時候**還沒有建 .venv/**。它只是產生設定檔,還沒有實際的環境。
uv init 不能用在已經有環境的專案
我一開始搞混了,跑到課程資料夾裡(已經有 .venv 的地方)直接打 uv init,結果它在課程目錄裡生了一堆不該出現的檔案。清理了一輪才搞定。
我問 AI:「如果我的既有專案需要 pyproject.toml 呢?」
答案是:uv init 是從零蓋新房子,不是在既有的房子裡加東西。如果你的專案已經有 .venv,只是缺一份 pyproject.toml,直接手動建一個文字檔就好,不需要什麼特別的指令。
整理一下分工:
| 情境 | 做法 |
|---|---|
| 全新專案,什麼都沒有 | uv init |
| 已有專案,要建或重建環境 | uv venv + uv pip install |
| 已有專案 + 已有 venv,想補一份 pyproject.toml | 手動建 pyproject.toml 這個檔案 |
uv add 跟 uv pip install 的差別
建好專案後,課程要我用 uv add 裝套件:
uv add numpy matplotlib
跑完之後輸出了三件事:
Using CPython 3.12.11
Creating virtual environment at: .venv
Installed 11 packages
第二行很關鍵:.venv 是在這一步才建的,不是 uv init 的時候。uv add 發現還沒有環境,就自動幫我建了一個。
我本來以為 uv add 跟 uv pip install 一樣,問了 AI 才知道差別:
| 指令 | 裝進 .venv | 寫進 pyproject.toml |
|---|---|---|
uv pip install numpy | 有 | 沒有 |
uv add numpy | 有 | 有 |
uv add 做兩件事:裝套件,同時把套件名稱寫進 pyproject.toml 的 dependencies 清單。uv pip install 只裝,不記。
我的理解是:uv pip install 是買了東西放進冰箱但沒寫購物清單,uv add 是買了東西、同時寫到清單上。我在 Lesson 1 用 uv pip install numpy matplotlib jupyter 裝的那些套件,其實就是「有冰箱但沒清單」的狀態。
pyproject.toml 裡面長什麼樣
uv add 跑完之後,cat pyproject.toml 看到的內容:
[project]
name = "my-ai-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"matplotlib>=3.11.1",
"numpy>=2.5.1",
]
每個欄位的意思:name 是專案名稱,version 是版本號,requires-python 是最低 Python 版本需求,dependencies 是這個專案需要的套件清單。
注意 dependencies 裡寫的是 >=(大於等於),不是 ==(等於)。代表「至少要這個版本,更新的也行」。
optional-dependencies:看需求才裝的套件
課程介紹了一個進階功能。如果有些套件不是每個人都需要,可以分組放在 optional-dependencies 裡:
[project.optional-dependencies]
torch = ["torch>=2.3", "torchvision>=0.18"]
llm = ["anthropic>=0.39", "openai>=1.50"]
安裝的時候用中括號指定要哪一組:
uv pip install -e ".[torch]" # 必裝的 + torch 那組
uv pip install -e ".[llm]" # 必裝的 + llm 那組
uv pip install -e ".[torch,llm]" # 全部
-e 是 editable 的意思,「可編輯模式」:裝這個專案本身,但改了程式碼不用重裝就生效。. 代表「當前資料夾的這個專案」。
這個設計在 AI 專案裡很常見。PyTorch 本身就要 2GB 以上,如果只是要呼叫 API,根本不需要裝它。分組讓每個人各取所需,寫在 pyproject.toml 裡也讓接手的人看到就知道有哪些選項。
套件名稱不等於專案名稱
寫 pyproject.toml 的時候我問了一個問題:「我能寫 pytorch>= 嗎?」
不行。PyTorch 這個專案的套件名稱是 torch,不是 pytorch。同樣的,Anthropic SDK 的套件名稱是 anthropic,不能寫成 anthropic SDK(套件名不能有空格)。判斷的方法是:你 uv pip install 的時候打什麼名字,pyproject.toml 裡就寫什麼。
Lockfile:為什麼 pyproject.toml 不夠
uv add 跑完之後,資料夾裡除了 pyproject.toml 還多了一個 uv.lock。我 cat uv.lock 看了一下,跑出來一大串東西,記了每一個套件的精確版本號、它依賴誰、從哪裡下載。
pyproject.toml 寫的是 numpy>=2.5.1,代表「2.5.1 以上都行」。但三個月後 numpy 出了新版,別人照你的清單裝就會裝到新版。如果新版有什麼改動讓你的程式壞掉,你就有問題了。lockfile 把每個套件釘死在精確版本,保證任何人任何時候照它裝,都跟你一模一樣。
我問 AI:「如果 lockfile 就能精確重建環境,那為什麼還需要 pyproject.toml?」
答案是:它們是給不同對象看的。
pyproject.toml 是給人看的。你打開就知道「這個專案用了 numpy 和 matplotlib」,要加套件、改版本範圍,改的是這個檔案。uv.lock 是給機器看的,你跑 uv add 它自動更新,人類不會去手動編輯它。
更根本的原因是:lockfile 是算出來的結果,pyproject.toml 是算的輸入。沒有輸入就沒辦法重算。如果你想放寬 numpy 的版本範圍,或加一個新套件,你改的是 pyproject.toml,然後 uv 根據它重新算出新的 lockfile。光有 lockfile,你連哪些是你主動裝的、哪些是被拖進來的依賴都分不出來。
我想了一下,覺得食譜跟收據的類比很好懂:pyproject.toml 是食譜,lockfile 是這次買菜的收據。你不能只留收據不留食譜,下次想換個牌子的材料,得改食譜重算。
三個檔案各自的角色
| 檔案 | 給誰看 | 要不要 commit 進 git |
|---|---|---|
pyproject.toml | 人 | 要 |
uv.lock | 機器(uv 自動生成) | 要 |
.venv/ | 你自己的電腦 | 不要(太大、路徑寫死、每個人自己建) |
常見錯誤:課程列了五個
裝到全域
沒有 activate venv 就 pip install,套件會裝到系統 Python 裡,所有專案共用,就回到了打架的狀態。
檢查方式是 which python。如果顯示 .venv/bin/python,你在 venv 裡;如果顯示 /usr/bin/python,你在系統全域。
pip 和 conda 混用
如果用 conda 建的環境,裡面的套件就全部用 conda 裝。混 pip 進去會讓 conda 的依賴追蹤亂掉。我目前沒有用 conda,這條先記著。
忘記 activate
我跑 which python3 的時候看到 /usr/bin/python3(系統的 Python 3.9.6),才發現自己忘了 activate。啟動之後路徑變成 .venv/bin/python,提示列前面也多了 (my-ai-project)。
source .venv/bin/activate 逐字拆開來看:source 是把檔案的內容讀進當前 shell 執行,.venv/bin/ 是 venv 裡放可執行檔的地方(bin 是 binary 的縮寫),activate 是一個 shell 腳本,它做的事就是改你的 PATH 環境變數,讓 python 這個字優先指向 .venv/bin/python。
用 source 而不是 bash 來跑 activate,是因為 bash 會開一個新的子 shell,跑完就關掉,你現在的 shell 不會改變。source 是直接改你正在用的 shell。
把 .venv commit 進 git
.venv/ 通常 200MB 到 2GB,而且裡面的路徑是寫死的(之前 Lesson 1 學過 venv 不能搬家),不應該進 git。在 .gitignore 裡加一行 .venv/ 就好。
加的指令是 echo ".venv/" >> .gitignore。>> 是附加到檔案尾端,不會蓋掉原本的內容。如果用 > 只有一個箭頭,會清空整個檔案再寫入。
CUDA 版本不匹配
PyTorch 編譯時會綁定特定的 CUDA 版本,如果跟你電腦 GPU driver 的版本對不上,就會裝了 PyTorch 卻用不了 GPU。我的 Mac 沒有 NVIDIA 顯卡,走 MPS,不會遇到這個問題。
跑 env_setup.sh
課程的 Use It 段落要你跑一個腳本:
bash phases/00-setup-and-tooling/06-python-environments/code/env_setup.sh
這個腳本做的事情是:檢查有沒有 uv → 檢查 Python 版本 → 建 .venv(已有就沿用) → 啟動 venv → 裝 core 套件(numpy、matplotlib、jupyter、scikit-learn、pandas)→ 逐一驗證 → 跑一個 NumPy 矩陣乘法確認正常 → 順便看有沒有 PyTorch。
我第一次跑的時候失敗了。腳本說 Python 3.11+ not found,因為它先檢查 Python 版本再建 venv,而我的系統全域 Python 是 Mac 內建的 3.9.6。解法是先 source .venv/bin/activate,讓 shell 指向 venv 裡的 Python 3.12,再跑腳本。
第二次就全部 PASS 了:
[PASS] uv found: uv 0.10.4
[PASS] Python: Python 3.12.11
[PASS] All checks passed
腳本也幫我補裝了之前沒有的 pandas、scikit-learn、scipy 等套件。
四個關鍵詞
| 術語 | 一般怎麼講 | 白話意思 |
|---|---|---|
| Virtual environment | 「a venv」 | 一個獨立的資料夾,裡面有自己的 Python 和套件,跟系統 Python 分開 |
| Lockfile | 「pinned dependencies」(釘死的依賴) | 記錄每個套件精確版本的檔案,讓任何人都能裝到一模一樣的環境 |
| pyproject.toml | 「the new setup.py」 | Python 專案的標準設定檔,取代了以前的 setup.py、setup.cfg、requirements.txt |
| Transitive dependency | 「a dependency of a dependency」(依賴的依賴) | 你裝 matplotlib,它背後需要 pillow、fonttools 等,這些你沒親手裝的就是 transitive dependency |
走到這裡才會知道的事
uv init 不帶名字會在當前目錄直接生檔案。 我一開始在課程資料夾裡直接打 uv init,結果它在那裡生了 pyproject.toml、main.py、.python-version,跟課程原本的檔案混在一起。清理了一輪才搞定。帶上名字 uv init my-ai-project 就會建一個子資料夾,不會污染外面。
env_setup.sh 在 activate venv 之前跑會失敗。 腳本的邏輯是先檢查 Python 版本,但 Mac 系統的 Python 是 3.9.6,不到它要求的 3.11+。先手動 source .venv/bin/activate 切到 venv 裡的 3.12,再跑腳本就行了。
這一課跟 Lesson 1 內容有重疊。 建 venv、裝套件、activate,兩課都做了。作者的設計是 Lesson 1 先讓你跑起來,Lesson 6 再教背後的原理和進階用法(pyproject.toml、lockfile、optional-dependencies)。如果你已經照 Lesson 1 建好了 .venv,這課的練習可以建在別的地方(像我建在 06-python-environments/my-ai-project/ 裡),練完刪掉就好。
uv add 時如果沒有 .venv/ 會自動建一個。 不需要先跑 uv venv。uv add 發現沒有環境,就自己處理了。但心裡要清楚哪一步做了什麼,不要把 uv init 和 uv add 的功能混在一起。