Python中的檔案操作

Python中的檔案操作

本文將解釋Python中的檔案操作。

本指南以「安全」、「高效」和「可讀性」為核心,從基礎到實用技術,逐步說明各項概念。

YouTube Video

Python中的檔案操作

檔案操作是從小型腳本到大型應用程式不可或缺的基礎技能。

打開與關閉檔案

首先,讓我們看看讀取與寫入文字檔案的範例。使用 with 陳述式(上下文管理器)可確保檔案被正確關閉。

以下程式碼會開啟一個文字檔案,讀取檔案內容,並逐行處理。

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • 明確指定 encoding="utf-8" 可以減少平台相關的問題。
  • 即使處理大型檔案,for line in f 也能有效節省記憶體。

寫入(覆寫與追加)

寫入檔案時請留意使用的模式。w 是覆寫,a 是追加。寫入時同樣建議使用 with 陳述式。

以下程式碼示範了覆寫與追加的基本用法。

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • 這段程式先將文字寫入 output.txt,再向同一檔案追加更多資料。"w" 模式會覆寫檔案,"a" 模式則會在現有內容最後追加新內容。
  • 如需立即寫入檔案,可呼叫 flush(),但通常在離開 with 區塊時會自動寫入。
  • 若可能有多個程序或執行緒同時寫入,請考慮排他控制(例如檔案鎖定)。

讀寫二進位資料

圖片及壓縮檔等需以二進位模式(rbwb)處理。與文字模式不同,二進位模式會忽略 encoding 設定。

以下程式碼會讀取一個二進位檔案,並以二進位模式複製到另一個新檔。

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • 處理大型二進位檔時,避免用 read() 一次全部讀入,分塊讀寫可以更有效率地利用記憶體。

分塊處理大型檔案的範例

無法一次載入記憶體的大型檔案,請以固定大小的區塊讀寫。因為這屬於 I/O 密集操作,調整緩衝區大小會有幫助。

以下程式碼以 64KB 為單位分塊複製檔案。這樣能保持低記憶體消耗且有較快效能。

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • 可依據網路或硬碟特性調整分塊大小。對於固態硬碟(SSD),較大的分塊通常表現更佳。

使用 buffering 參數的範例

通過在 open() 函數中指定 buffering 參數,您可以控制 Python 內部使用的緩衝區大小。這使您能進一步優化輸入/輸出效率。

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • 如果您將 buffering 的值設為 0,I/O 將不使用緩衝區;設為 1 可啟用行緩衝,而 2 或更高的值則會使用指定大小(以位元組為單位)的緩衝區。
  • 一般來說,預設值已經足夠,因為作業系統會有效率地處理快取,但對於非常大的檔案或特殊裝置,調整這個參數可能會更有效。

使用 Pathlib 進行現代化檔案處理

標準模組 pathlib 讓路徑處理更直覺。與字串路徑相比,能提升程式的可讀性和安全性。

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • 這段程式碼示範用 pathlib 建立資料夾、寫入文字檔案及讀取其內容。利用 Path 物件,你可以更直觀且安全地處理路徑。
  • Path 提供如 iterdir()glob() 等便捷 API。跨作業系統時,不用擔心路徑分隔符號的不同。

暫存檔案與資料夾(tempfile)

可利用 tempfile 安全地建立暫存檔案。這樣可以避免安全性競爭問題及檔名衝突。

以下程式為使用暫存檔案建立暫存資料的範例。

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • 這段程式會建立暫存檔,寫入、讀取資料,並在 with 結束時自動刪除。利用 tempfile.NamedTemporaryFile 可安全且不衝突地操作暫存檔。由於設置了 delete=True,檔案會自動刪除。
  • 在 Windows 上,無法立刻用其他 handle 開啟檔案時,建議設定 delete=False 並自行管理刪除時機。

shutil:高階的檔案複製、移動與刪除操作

使用 shutil 可以輕鬆遞迴複製、移動及刪除檔案和資料夾。

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 也會複製檔案的中繼資料(例如修改時間)。move 若無法直接重新命名,會退而求其次進行複製與刪除。
  • rmtree 屬於高風險操作,刪除前請務必再次確認並備份。

檔案中繼資料(os.stat)與權限處理

可透過 osstat 讀取和修改檔案大小、修改時間及權限。

以下程式碼先用 os.stat 取得檔案資訊,再以 os.chmod 修改權限。

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • 權限控制在 POSIX 與 Windows 系統行為有所不同。若需要跨平台相容性,請考慮高階 API 或加入條件判斷。

檔案鎖定(排他控制)— Unix 與 Windows 差異

有多個程序平行存取同一檔案時需進行排他控制。UNIX 使用 fcntl,Windows 則採用 msvcrt

以下程式於 UNIX 系統利用 fcntl.flock 在寫入時取得檔案鎖。

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • 這段程式在類 UNIX 系統利用 fcntl.flock 實現排他鎖,有效防止同時寫入。處理完後務必釋放鎖定,以便其他程序能正常存取。
  • 在 Windows 上,請使用 msvcrt.locking()。若需更高階功能,可考慮 portalockerfilelock 等第三方套件。

原子性檔案寫入模式

為避免檔案更新過程損毀,請先寫入暫存檔,完成後用 os.replace 取代。

先寫入暫存檔再取代,可防止寫入途中程式當機造成文件損毀。

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

os.replace 能於同一檔案系統內完成原子性取代。請注意:不同掛載點間不保證原子性。

利用 mmap 進行快速存取(適合大規模資料)

對大型檔案要隨機存取時,mmap 可提升 I/O 效率。主要適用於二進位操作。

以下程式碼利用記憶體映射檔案,可讀寫指定位元組範圍。更改檔案大小時須特別留意。

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • 這段程式將二進位檔以 mmap 映射後,可直接進行位元組級存取。記憶體映射可讓大數據集快速隨機存取。
  • mmap 雖然效率高,但誤用可能導致資料一致性問題。有需要時請呼叫 flush() 以同步資料。

CSV / JSON / Pickle:依格式讀寫資料

特定資料格式有專用模組。使用 csv 處理 CSV,json 處理 JSON,pickle 儲存 Python 物件。

以下程式碼示範基本的 CSV、JSON 讀寫及 Pickle 用法。由於 Pickle 可能執行任意程式碼,請勿讀入不可信來源的資料。

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • 建議 CSV 檔案使用 newline="" 以避免 Windows 系統出現多餘的空白行。設 ensure_ascii=False 可讓 JSON 保留可讀的 UTF-8 字元。

直接讀寫壓縮檔案(gzip / bz2 / zipfile)

直接操作 gzip 與 zip 檔能節省磁碟空間。標準函式庫內提供相關模組。

以下提供以文字方式讀寫 gzip 檔案的簡單範例。

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • 根據壓縮等級和格式,壓縮率與速度之間存在權衡。

安全性與漏洞對策

以下幾點可作為安全性與漏洞對策的考量。

  • 切勿將不可信的輸入直接用於檔名或路徑。
  • 只應在可信來源下使用 Pickle
  • 請盡量降低執行權限,僅賦予檔案作業必要的最小權限。
  • 請使用 tempfile 建立暫存檔,不要直接將明文檔案放在公開目錄。

若將使用者輸入當作檔案路徑,必須正規化且驗證。例如,可用 Path.resolve() 並檢查父層目錄。

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • 在 Web 應用程式或公開 API 中使用外部輸入作為路徑時,尤其要謹慎。

常見模式總結

  • 務必使用 with 陳述式(自動關閉)。
  • 針對文字資料,請明確指定 encoding
  • 大型檔案請採分塊讀寫。
  • 為共享資源導入檔案鎖定機制。
  • 針對重要更新,請使用「寫入暫存檔 → os.replace」的原子性操作模式。
  • 在執行危險操作(如刪除、覆寫)前,務必確認並製作備份。
  • 在將外部輸入作為檔案路徑時須正規化並驗證。

總結

進行檔案操作時,應採用安全可靠的方法,例如使用 with 陳述式、明確指定編碼,以及原子性寫入。對於大型處理或平行存取,必須實作鎖定和日誌管理系統,以防止資料損毀和衝突。兼顧效率與安全是確保可靠檔案操作的關鍵。

您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。

YouTube Video