跳到主要內容

Day 11 檔案讀寫與例外處理

Day 11 檔案讀寫與例外處理

引言

到目前為止,我們的 Python 程式都在記憶體裡運作:變數、串列、字典這些資料,只要程式結束就消失了。如果想讓資料留下來,就得把結果寫到磁碟;想讀別人整理好的資料,也得從檔案讀進來。檔案讀寫是寫程式很常見的工作,不論是記錄使用者輸入、讀取設定檔、輸出報表,或是處理資料科學的 CSV,都脫離不了檔案操作。可以說,學會檔案讀寫之後,Python 才真正從「練習小工具」升級成「能處理真實資料的工具」。

不過,現實世界的檔案操作常常出問題:檔案可能不存在、權限不足、磁碟滿了,或讀到一半網路斷線。這時候如果沒有處理例外,程式就會直接當機,並把一段落落長的 Traceback 丟到使用者面前。今天除了學會基本的檔案讀寫,也會介紹 try / except 的機制,讓程式在出錯時能優雅地回應,而不是整支停下來。學完之後,你就能寫出對錯誤有耐受度的小工具,給同事或自己使用時也更安心。

讀取檔案:open() 與 with 敘述

Python 提供內建的 open() 函式來開啟檔案。最重要的兩個參數是檔案路徑與開啟模式:'r' 表示讀取(預設)、'w' 表示寫入、'a' 表示附加在檔尾。開啟後回傳的是一個檔案物件,可以呼叫 read()、readline()、readlines() 等方法讀取內容。讀取完成後要記得呼叫 close() 關閉檔案,否則可能會遺失部分資料、檔案被鎖住無法刪除,甚至在 Windows 上直接跳出「檔案已被另一個程式使用」的錯誤。

實務上更推薦用 with 敘述(context manager)。它會在區塊結束時自動關閉檔案,即使中間發生錯誤也會正確釋放資源。這種寫法不僅更安全,也讓程式碼更乾淨,省下手動呼叫 close() 的一行。

以下範例假設工作目錄下有一個 notes.txt(內容是幾行文字);如果手邊沒有這個檔案,可以先建立一個再跟著執行。

# 讀取整個檔案
with open("notes.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# 一次讀一行,逐行處理
with open("notes.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())  # 去除換行與空白

第一段把整個檔案讀成一個字串,適合內容不大的設定檔或小報告。第二段用 for 迴圈逐行走訪,處理大檔案時比較省記憶體。常見的 encoding="utf-8" 是為了正確讀寫中文,避免在不同作業系統間產生亂碼。Windows 預設的編碼是 cp950 或 cp1252,如果沒指定 encoding,可能會在中文環境讀到 Mojibake(亂碼)字元。

如果只想讀取前幾行或前幾個字元,也可以用 readline() 與 readlines():

with open("notes.txt", "r", encoding="utf-8") as f:
    first_line = f.readline()
    rest_lines = f.readlines()  # 回傳剩餘行組成的串列
    print(first_line)
    print(rest_lines[:3])  # 只看前 3 行

readline() 一次讀一行(含換行符號),適合用在「讀到某個關鍵字就停止」這類流程控制。readlines() 則一次把所有剩餘行讀成串列,方便後續用索引或切片處理。如果要讀取的檔案很大(幾 GB),建議改用前面示範的逐行 for 迴圈,避免一次把所有內容塞進記憶體。

寫入檔案:覆寫與附加

開啟檔案時的模式決定了寫入行為。'w' 模式會建立新檔案或覆寫既有檔案,'a' 模式則會把內容附加到檔案尾端。要小心 'w' 會直接清空原本的內容,如果是想保留舊資料又想寫入新的,必須先讀出來合併再寫回去,或者改用 'a' 模式。寫入可以用 write()(寫入字串)或 writelines()(寫入一組字串,但 writelines 不會自動加換行)。

# 寫入新檔
lines = ["第一行\n", "第二行\n", "第三行\n"]
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello, Python!\n")
    f.writelines(lines)

# 附加內容到既有檔案
with open("output.txt", "a", encoding="utf-8") as f:
    f.write("這是附加的一行\n")

執行後,output.txt 會包含四行文字。注意 write() 不會自動加上換行符號,需要自己寫 "\n",這是新手常見的坑。如果同時需要讀取與寫入,可以使用 'r+' 或 'w+' 等模式,但實務上多半是先讀後寫,分成兩段 with 區塊處理會更清楚,也比較不容易出錯。當需要寫入更複雜的結構(例如字典、串列)時,可以考慮用 json 模組,它能直接把 Python 物件序列化為文字並儲存,之後再讀回來還原,維護成本比純文字檔低很多。

例外處理基礎:try / except / else / finally

開啟檔案時最常見的錯誤就是 FileNotFoundError:指定的檔案不存在。這時候如果沒有處理,程式會立刻中斷並印出 Traceback,體驗很差。Python 用 try / except 來捕捉並處理例外(exception)。try 區塊裡放「可能出錯的程式碼」,except 區塊裡放「錯誤發生時要做的事」。

try:
    with open("missing.txt", "r", encoding="utf-8") as f:
        content = f.read()
        print(content)
except FileNotFoundError:
    print("找不到檔案,請確認路徑是否正確")
except PermissionError:
    print("沒有讀取權限")
except Exception as e:
    print(f"發生未預期的錯誤:{e}")

這個範例展示了幾個重點:except 後面可以指定例外型別,只處理我們關心的錯誤;多個 except 由上而下依序匹配,越具體的例外放越前面;最後一個 Exception 是所有例外的父類別,用來當作保險,攔截前面沒接住的錯誤。實際上,最好不要用「裸 except」也就是不指定任何錯誤別的寫法,因為連 KeyboardInterrupt 都被吃掉,會讓程式難以中斷。

完整的 try 結構還包含 else 與 finally:else 在沒有發生例外時執行,finally 則無論是否出錯都會跑,通常用來釋放資源或紀錄狀態。

try:
    f = open("data.txt", "r", encoding="utf-8")
    content = f.read()
except FileNotFoundError:
    print("檔案不存在")
else:
    print(f"成功讀取,內容長度:{len(content)}")
finally:
    if "f" in locals() and not f.closed:
        f.close()
    print("讀檔流程結束")

把「可能成功才要做的事」放在 else,能讓主流程更清楚,避免例外處理區塊被太多「正常邏輯」塞住。finally 則很適合放紀錄檔(log)寫入或資源釋放,確保不管程式怎麼走,這些事情一定會被執行。實際專案中,用 with 敘述搭配 try / except 是最常見的組合。

自訂例外與 raise

有時候我們想主動觸發錯誤,而不是被動等 Python 丟例外。例如使用者輸入的年齡是負數,這在邏輯上不合理,應該告訴呼叫端這個狀況。這時候可以用 raise 主動拋出例外。raise 後面接例外實例(可以是內建的,也可以是自訂的),程式會從這個位置立刻跳離當前呼叫堆疊。

def set_age(age):
    if not isinstance(age, int):
        raise TypeError("年齡必須是整數")
    if age < 0 or age > 150:
        raise ValueError("年齡超出合理範圍 (0-150)")
    print(f"設定年齡:{age}")

try:
    set_age(-5)
except ValueError as e:
    print(f"錯誤:{e}")

主動拋出例外比「回傳 -1 代表錯誤」這種用約定俗成的特殊值好得多,因為它強迫呼叫端必須處理,無法不小心忽略。實務上也常在函式入口做參數檢查,盡量把錯誤擋在離問題最近的地方,讓問題的根源更明確。

當專案變大,我們可以自訂例外類別,讓錯誤訊息更貼近業務情境。自訂例外只要繼承 Exception 或它的子類別即可。建議繼承最接近語意的內建例外(例如值錯誤就繼承 ValueError),這樣呼叫端既可以選擇針對特定自訂例外處理,也可以選擇針對父類別處理。

class InvalidAgeError(Exception):
    """年齡不合理的例外"""
    pass

def register(name, age):
    if age < 0:
        raise InvalidAgeError(f"使用者 {name} 的年齡不可小於 0")
    print(f"註冊 {name} ({age} 歲)")

try:
    register("小明", -1)
except InvalidAgeError as e:
    print(f"註冊失敗:{e}")

自訂例外的好處是呼叫端可以針對特定錯誤型別做處理,不會被一堆內建例外淹沒,閱讀程式碼時也能更快理解錯誤的商業意義。一個專案通常會把自訂例外集中在 exceptions.py 或 errors.py 模組,方便統一管理。

結語

今天學會了 Python 處理檔案與例外的基本功:用 open() 搭配 with 敘述來讀寫檔案,用 try / except / finally 來應對錯誤,再用 raise 與自訂例外把錯誤訊息講清楚。這幾個工具是寫任何真實程式都會用到的,特別是資料處理流程,常常得面對「檔案不存在」「格式怪怪」「資料缺漏」等狀況,把例外處理好,程式會更可靠。建議實際找一個過去寫過的小工具,把檔案讀寫與例外處理補進去,感受一下前後差異。

明天,我們會進入模組與套件的世界,看看怎麼把程式拆成可重複使用的模組、用 pip 安裝第三方套件,以及用 venv 為不同專案建立獨立的 Python 環境。這些基礎會讓後續安裝 NumPy、Pandas 與 PyTorch 時更順暢。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...