從明天開始,我們要讓程式連上外面的世界——查天氣、呼叫 AI。而幾乎所有這類服務都需要一把「鑰匙」:API Key。
在開始之前,今天要先處理一件很容易被忽略、但出事會很慘的事:這把鑰匙該放在哪裡。
最直覺的寫法是這樣:
API_KEY = "sk-ant-api03-xxxxxxxxxxxxxxxxxxxx" # ❌ 千萬不要
response = call_api(API_KEY, ...)
能動,但有三個問題:
1. 推上 GitHub 就等於公開了
這是最常見的意外。你把程式碼 push 到公開 repo,key 就在全世界面前。而且就算你事後 commit 一次把它刪掉,git 歷史裡還留著——任何人 clone 下來都翻得到。
GitHub 有自動掃描機制,很多服務商也會掃。運氣好的話你會收到「你的 key 外洩了,我們幫你停用」的信;運氣不好的話,就是帳單。
2. 不同環境要用不同的 key
開發時用測試 key,正式上線用正式 key。如果 key 寫死在程式碼裡,你就得改程式碼才能切換。
3. 分享程式碼變得很麻煩
朋友想跑你的程式,但他要用自己的 key,他得去程式碼裡面找哪一行要改。
正確的做法只有一個原則:
把「程式碼」和「設定」分開。程式碼可以公開,設定不行。
環境變數是作業系統層級的一組 key-value,程式可以讀取它。Python 用 os.environ 存取:
import os
api_key = os.environ.get("ANTHROPIC_API_KEY")
print(api_key)
os.environ 用起來就像一個 dict。注意兩種讀法的差別:
os.environ["ANTHROPIC_API_KEY"] # 不存在 → KeyError(程式炸掉)
os.environ.get("ANTHROPIC_API_KEY") # 不存在 → None
os.environ.get("CITY", "台北") # 不存在 → 回傳預設值「台北」
大部分情況用 .get() 比較安全(Day 8 的精神)。
Windows(PowerShell,只在這個視窗有效):
$env:ANTHROPIC_API_KEY = "sk-ant-..."
Windows(永久設定,重開終端機才生效):
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-...", "User")
macOS / Linux:
export ANTHROPIC_API_KEY="sk-ant-..."
但這樣有個困擾:每次重開終端機都要重設一次(除非設成永久)。而且不同專案要用不同的 key 時,全域環境變數就很卡。
所以實務上更常用的是下一個做法。
在專案資料夾裡放一個叫 .env 的檔案:
# .env
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxx
CWA_API_KEY=CWA-XXXXXXXX-XXXX-XXXX
DEFAULT_CITY=臺北市
DEBUG=true
格式規則很簡單:
KEY=VALUE
# 開頭是註解然後用 python-dotenv 套件把它載進環境變數:
pip install python-dotenv
import os
from dotenv import load_dotenv
load_dotenv() # 讀取 .env,把內容塞進 os.environ
api_key = os.environ.get("ANTHROPIC_API_KEY")
city = os.environ.get("DEFAULT_CITY", "臺北市")
load_dotenv() 要在程式的最開頭呼叫,在任何 os.environ.get() 之前。
.env 檔案絕對不能進版本控制。在專案根目錄建立 .gitignore:
# 環境變數
.env
.env.local
.env.*.local
# Python
__pycache__/
*.py[cod]
.venv/
venv/
# 資料與輸出
data/
logs/
*.log
# 編輯器
.vscode/
.idea/
.DS_Store
這樣 git 就會忽略這些檔案,git add . 也不會把它們加進去。
💡 **養成習慣:專案一建立,第一件事就是寫
.gitignore。**不要等到有東西要 commit 了才想起來——那時候可能已經來不及了。
建立一個 .env.example,這個檔案要進版本控制:
# .env.example
# 複製這個檔案成 .env,然後填入自己的值
# Anthropic API Key,到 console.anthropic.com 申請
ANTHROPIC_API_KEY=
# 中央氣象署 API Key,到 opendata.cwa.gov.tw 申請
CWA_API_KEY=
# 預設城市
DEFAULT_CITY=臺北市
只有 key 的名字和說明,沒有真正的值。新人 clone 專案之後:
cp .env.example .env # Windows: copy .env.example .env
然後填入自己的 key 就能跑了。
把所有設定集中在一個地方管理,是很值得的投資。建立 config.py:
"""集中管理所有設定與環境變數。"""
import os
from pathlib import Path
from dotenv import load_dotenv
# 專案根目錄(config.py 所在的資料夾)
BASE_DIR = Path(__file__).resolve().parent
# 明確指定 .env 的位置,避免從不同目錄執行時找不到
load_dotenv(BASE_DIR / ".env")
def _require(key: str) -> str:
"""讀取必填的環境變數,沒有就直接報錯。"""
value = os.environ.get(key)
if not value:
raise RuntimeError(
f"缺少環境變數 {key}。\n"
f"請複製 .env.example 成 .env,並填入 {key} 的值。"
)
return value
def _optional(key: str, default=None):
return os.environ.get(key, default)
def _bool(key: str, default=False) -> bool:
value = os.environ.get(key)
if value is None:
return default
return value.strip().lower() in ("1", "true", "yes", "on")
# --- API Keys ---
ANTHROPIC_API_KEY = _require("ANTHROPIC_API_KEY")
CWA_API_KEY = _optional("CWA_API_KEY")
# --- 一般設定 ---
DEFAULT_CITY = _optional("DEFAULT_CITY", "臺北市")
DEBUG = _bool("DEBUG", False)
# --- 路徑 ---
DATA_DIR = BASE_DIR / "data"
LOG_DIR = BASE_DIR / "logs"
DATA_DIR.mkdir(exist_ok=True)
LOG_DIR.mkdir(exist_ok=True)
用起來:
import config
print(config.DEFAULT_CITY)
print(config.DATA_DIR / "todos.json")
這個模組有幾個設計重點:
1. _require() 讓錯誤「早一點」發生
如果沒設 key,程式在啟動時就報錯,而且訊息很清楚。不然的話,你會跑到一半才收到一個 401 Unauthorized,還要猜是 key 錯了還是沒設。
這是 Day 8 講的 raise 精神:寧可早點炸,也不要默默往下走。
2. BASE_DIR = Path(__file__).resolve().parent
__file__ 是「這個 .py 檔的路徑」。這樣寫可以保證不管你從哪個目錄執行程式,路徑都對。用相對路徑 Path(".") 的話,從不同目錄跑就會找不到檔案。
3. 環境變數都是字串
DEBUG=true 讀出來是字串 "true",不是布林值 True。而 bool("false") 的結果是 True(非空字串都是 True)!所以要自己寫 _bool() 轉換。這個坑很多人踩過。
1. 不要把 key 印出來
print(f"使用 key: {api_key}") # ❌ 會出現在 log 裡
print(f"使用 key: {api_key[:8]}...") # ✅ 只印前幾碼確認
log 檔案常常會被上傳、被分享、被 AI 讀取。key 一旦進了 log,就等於外洩了。
2. key 外洩了就立刻作廢
不要想著「應該沒人看到吧」。到服務商的後台把那把 key revoke,重新產一把。所有主流服務都支援這個操作,而且是免費的。
3. 給 key 最小權限
很多服務可以設定 key 的權限範圍和額度上限。只需要讀資料的 key,就不要給它寫入權限。
4. 讓 AI 幫你寫程式時也要注意
現在很多人(包括我)會用 AI 輔助寫程式。貼程式碼給 AI 之前,先確認裡面沒有真的 key。如果你用的是 os.environ.get(),這個問題就自動解決了——這也是好習慣的額外好處。
到這裡,我們的專案架構大概會長這樣:
life-assistant/
├── .env ← 真正的設定(不進 git)
├── .env.example ← 範本(進 git)
├── .gitignore
├── config.py ← 設定模組
├── main.py ← 程式進入點
├── tools/ ← 各種工具(Day 15 開始)
│ ├── __init__.py
│ └── weather.py
├── data/ ← 資料檔(不進 git)
│ ├── todos.json
│ └── preferences.json
└── logs/ ← 執行日誌(不進 git)
這個結構會一路用到 Day 30 的完整專案。
先把資料夾建好:
mkdir life-assistant
cd life-assistant
mkdir tools data logs
直接的關係是:接下來每一個外部服務都需要 key。天氣 API、AI API、行事曆 API——沒有妥善的 key 管理,後面寸步難行。
間接但更重要的關係是:Agent 是會自己做決定的程式。
想一下這個場景:
你做了一個 Agent,它可以讀寫檔案、呼叫 API、發送訊息。然後你把程式碼丟給 AI 說「幫我改一下這裡」,或是你把它部署到某個雲端平台。
一個能自主行動的程式,掌握著你的憑證。這代表:
這些原則我們在 Day 25(Workflow 與 Agent 的分界)和 Day 29(整合測試)還會再回來談。今天先把最基本的做對:把鑰匙藏好。
.env 檔案管理設定,用 python-dotenv 載入.gitignore 要先寫,把 .env、data/、logs/ 都排除掉.env.example 讓別人知道要設什麼config.py 集中管理,必填的變數用 raise 早點報錯明天終於要連上網路了——HTTP 與 API 基礎,我們來看看程式怎麼跟外面的世界對話。