Python 檔案讀寫讓程式能與外部世界交換資料——讀取歷史 K 線、儲存回測結果、匯出交易紀錄,都仰賴 open() 函數與正確的開檔模式。本文完整解析 open() 的四種模式(r、w、a、rb/wb)、with 語法的安全開檔慣例、逐行讀取與整體讀取的差異,以及用 Python 內建 csv 模組讀寫 CSV 格式的量化資料完整實戰。
什麼是檔案讀寫?
Python 的檔案讀寫(File I/O)透過內建的 open() 函數操作,指定檔案路徑與開檔模式後,取得一個檔案物件,再透過 read()、write()、readline() 等方法與檔案內容互動。用完之後必須呼叫 close() 關閉檔案,釋放系統資源。
最佳實踐是使用 with 語法(情境管理器)自動管理開關檔案,就像煉金坊的自動封蓋熔爐:取用原料(讀寫資料)完畢後,爐蓋自動密封,不需要手動記得關閉,避免因忘記 close() 而造成資源洩漏或檔案損壞。
核心語法:open()、模式與 with
# === open() 四種常用模式 ===
# 'r' → 讀取(Read):檔案必須存在,否則 FileNotFoundError
# 'w' → 寫入(Write):覆蓋整個檔案;檔案不存在時自動建立
# 'a' → 附加(Append):在檔案末尾追加;檔案不存在時自動建立
# 'x' → 獨占建立:檔案已存在時拋出 FileExistsError
# 'rb' / 'wb' → 二進位讀寫(用於圖片、PDF 等非文字檔)
# === 不推薦的傳統寫法(容易忘記 close)===
f = open('data.txt', 'r', encoding='utf-8')
content = f.read()
f.close() # 必須手動關閉,若中途例外就不會執行到這裡!
# === ✅ 推薦:with 語法(自動關閉,安全)===
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# with 區塊結束後,f 自動關閉,無論是否發生例外
print(content)
開檔時務必指定encoding='utf-8'(或'utf-8-sig'處理 BOM 標頭),否則在不同作業系統上可能因預設編碼不同而出現亂碼,尤其是包含中文字元的檔案。Windows 預設編碼為cp950,Mac/Linux 為utf-8,明確指定才能跨平台相容。
# === 寫入檔案:'w' 模式(覆蓋)===
lines = [
"日期,開盤,高點,低點,收盤\n",
"2024-01-02,182.5,185.9,181.0,185.2\n",
"2024-01-03,185.5,188.2,184.7,187.8\n",
"2024-01-04,187.2,188.5,182.1,183.5\n",
]
with open('ohlc.txt', 'w', encoding='utf-8') as f:
f.writelines(lines) # 一次寫入多行(List)
print("寫入完成")
# write() 寫入單一字串
with open('log.txt', 'w', encoding='utf-8') as f:
f.write("策略啟動\n")
f.write("載入資料完成\n")
# === 附加檔案:'a' 模式(不覆蓋,追加到末尾)===
with open('log.txt', 'a', encoding='utf-8') as f:
f.write("2024-01-02 執行買入:AAPL x 100\n")
f.write("2024-01-03 執行賣出:AAPL x 100\n")
'w'模式每次都會清空並覆蓋整個檔案,若只需要在既有內容後面追加(如交易日誌),必須改用'a'模式,否則之前的記錄會全部消失。
# === 讀取方式比較 ===
# read():一次讀取整個檔案成字串(適合小檔案)
with open('ohlc.txt', 'r', encoding='utf-8') as f:
all_text = f.read()
print(all_text)
# readlines():一次讀取所有行,回傳 List(每行是一個元素,含 \n)
with open('ohlc.txt', 'r', encoding='utf-8') as f:
all_lines = f.readlines()
print(all_lines[0]) # '日期,開盤,高點,低點,收盤\n'
# ✅ 逐行迭代(最省記憶體,適合大檔案)
with open('ohlc.txt', 'r', encoding='utf-8') as f:
for line in f: # 直接迭代檔案物件
print(line.strip()) # strip() 去除行尾 \n
規則與注意事項
| 模式 | 行為 | 檔案不存在時 |
|---|---|---|
'r' |
唯讀,不可寫入 | FileNotFoundError |
'w' |
寫入,清空舊內容 | 自動建立新檔案 |
'a' |
追加,保留舊內容 | 自動建立新檔案 |
'r+' |
讀寫,不清空 | FileNotFoundError |
'x' |
建立新檔案並寫入 | 自動建立;已存在則 FileExistsError |
常見錯誤與防呆
錯誤一:忘記指定 encoding,中文亂碼
# ❌ 錯誤寫法(Windows 預設 cp950,Mac/Linux 預設 utf-8,跨平台亂碼)
with open('data.txt', 'r') as f:
content = f.read() # 可能出現 UnicodeDecodeError 或亂碼
# ✅ 正確寫法:永遠明確指定編碼
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 若 CSV 有 BOM 標頭(Excel 存出的 UTF-8 CSV)
with open('data.csv', 'r', encoding='utf-8-sig') as f:
content = f.read()
錯誤二:用 'w' 模式誤刪既有資料
# ❌ 危險寫法(每次執行都清空 log 檔案!)
with open('trade_log.txt', 'w', encoding='utf-8') as f:
f.write("2024-01-05 買入 AAPL\n") # 之前的記錄全被刪除
# ✅ 正確做法:交易日誌用 'a' 模式追加
with open('trade_log.txt', 'a', encoding='utf-8') as f:
f.write("2024-01-05 買入 AAPL\n") # 保留所有歷史記錄
錯誤三:讀取不存在的檔案未加例外處理
# ❌ 錯誤寫法(檔案不存在時程式崩潰)
with open('missing.csv', 'r', encoding='utf-8') as f:
data = f.read() # FileNotFoundError,程式中止
# ✅ 正確做法:用 try-except 搭配檔案讀寫
import os
filepath = 'missing.csv'
if os.path.exists(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
data = f.read()
else:
print(f"檔案不存在:{filepath}")
data = None
# 或直接用 try-except
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = f.read()
except FileNotFoundError:
print(f"找不到檔案:{filepath}")
data = None
進階用法
csv 模組:讀寫結構化資料
Python 內建 csv 模組專門處理逗號分隔值格式,自動處理引號跳脫、分隔符號等邊界情況,比手動用 split(',') 分割更安全可靠,是量化資料處理的必備工具。
import csv
# === 寫入 CSV ===
ohlc_data = [
['Date', 'Open', 'High', 'Low', 'Close', 'Volume'],
['2024-01-02', 182.5, 185.9, 181.0, 185.2, 45000000],
['2024-01-03', 185.5, 188.2, 184.7, 187.8, 52000000],
['2024-01-04', 187.2, 188.5, 182.1, 183.5, 38000000],
['2024-01-05', 183.8, 186.3, 183.0, 185.9, 41000000],
]
with open('aapl_ohlc.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(ohlc_data) # 一次寫入多行
print("CSV 寫入完成")
# === 讀取 CSV ===
with open('aapl_ohlc.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader) # 讀取標題行
print(f"欄位:{header}")
for row in reader:
date, o, h, l, c, vol = row
print(f"{date} 收盤:{float(c):.2f} 成交量:{int(vol):,}")
csv.DictReader / DictWriter:欄位名稱存取
DictReader 讓每一行資料以字典形式讀取,用欄位名稱而非索引取值,程式碼更易讀,也不怕欄位順序改變;DictWriter 則讓寫入時以字典指定欄位值。
import csv
# === DictReader:以欄位名稱讀取 ===
closes = []
with open('aapl_ohlc.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # 自動把第一行當標題
for row in reader:
# row 是 OrderedDict,用欄位名稱取值
closes.append(float(row['Close']))
print(f"收盤價序列:{closes}")
print(f"最高收盤:{max(closes)}")
print(f"平均收盤:{sum(closes)/len(closes):.2f}")
# === DictWriter:以欄位名稱寫入 ===
results = [
{'Date': '2024-01-02', 'Signal': 'HOLD', 'PnL': 0},
{'Date': '2024-01-03', 'Signal': 'BUY', 'PnL': 0},
{'Date': '2024-01-04', 'Signal': 'HOLD', 'PnL': 2.6},
{'Date': '2024-01-05', 'Signal': 'SELL', 'PnL': 2.4},
]
fieldnames = ['Date', 'Signal', 'PnL']
with open('backtest_result.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() # 寫入標題行
writer.writerows(results) # 寫入所有資料行
print("回測結果已儲存")
量化實戰:完整回測結果讀寫流程
將回測的每日損益記錄寫入 CSV,之後再讀回計算績效指標,是量化開發中最基本也最重要的資料持久化流程。
import csv
import os
def save_trades(trades, filepath='trades.csv'):
"""儲存交易紀錄到 CSV"""
fieldnames = ['date', 'ticker', 'action', 'price', 'shares', 'pnl']
file_exists = os.path.exists(filepath)
with open(filepath, 'a', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
if not file_exists:
writer.writeheader() # 只有建立新檔案時才寫標題
writer.writerows(trades)
def load_trades(filepath='trades.csv'):
"""從 CSV 讀取交易紀錄並計算績效"""
if not os.path.exists(filepath):
print("找不到交易紀錄檔案")
return []
trades = []
try:
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
row['price'] = float(row['price'])
row['shares'] = int(row['shares'])
row['pnl'] = float(row['pnl'])
trades.append(row)
except Exception as e:
print(f"讀取失敗:{e}")
return trades
# 模擬使用
sample_trades = [
{'date': '2024-01-03', 'ticker': 'AAPL', 'action': 'BUY',
'price': 185.5, 'shares': 100, 'pnl': 0},
{'date': '2024-01-05', 'ticker': 'AAPL', 'action': 'SELL',
'price': 188.2, 'shares': 100, 'pnl': 270.0},
]
save_trades(sample_trades)
loaded = load_trades()
total_pnl = sum(t['pnl'] for t in loaded)
wins = [t for t in loaded if t['pnl'] > 0]
print(f"總交易筆數:{len(loaded)}")
print(f"獲利交易:{len(wins)}")
print(f"總損益:{total_pnl:.2f}")
煉金坊小叮嚀
恭喜你走到了 Python 語法系列的最後一篇!檔案讀寫是程式與外部世界溝通的橋樑,在量化開發中幾乎每天都用到。有三個習慣從一開始就要養成:第一,永遠用 with 開檔,不要手動 open + close;第二,永遠明確指定 encoding='utf-8',避免跨平台亂碼;第三,寫入交易日誌或追加記錄時確認用的是 'a' 而非 'w' 模式——我曾因一個 'w' 的筆誤,把幾個月的回測日誌清空,教訓深刻。對於量化資料建議優先使用 csv.DictReader / DictWriter,用欄位名稱取值比索引更健壯,之後加欄位不會讓程式碼崩潰。當資料量大時,進一步考慮使用 pandas 的 read_csv(),但掌握原生 csv 模組才能真正理解底層運作原理。