HOWTO · Python

用 Python 向檔案寫入位元組

本教程演示瞭如何在 Python 中向二進位制檔案寫入位元組。

本頁內容

在 Python 中將位元組寫入檔案時,使用 open() 搭配 wb 模式開啟檔案,並將類位元組物件傳給檔案的 write() 方法。使用 with 區塊可在結束時自動關閉檔案。若要在現有內容後方追加資料,請改用 ab。

每個程式碼範例都會在暫存目錄中建立檔案。範例的 with 區塊結束時,Python 會移除該目錄。若要保留檔案,請將暫存路徑換成你的目的地路徑。

在 Python 中將位元組寫入檔案

wb 中的 w 表示以寫入模式開啟檔案:檔案不存在時會建立檔案;若檔案已存在,則會在開啟時立即清空原有內容。b 表示使用二進位模式,因此 Python 會直接寫入位元組,不會進行文字編碼或換行轉換。

執行以下範例,寫入資料後再讀取回來:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "example.bin"
    data = b"This is some binary data."

    with open(path, "wb") as file:
        written = file.write(data)

    with open(path, "rb") as file:
        saved = file.read()

    print(written)
    print(saved == data)
25
True

對一般的緩衝檔案而言,write() 會傳回寫入的位元組數。程式會印出這個數量,並比較讀回的資料是否與原始資料相同。寫入區塊結束、檔案關閉並排清 Python 緩衝區後,才會進行讀取。.bin 副檔名只是命名慣例;是否使用二進位模式取決於模式引數。

若只想在檔案尚不存在時建立它,請使用 xb。與 wb 不同,若目的地已存在,xb 會拒絕覆寫。這兩種模式都不會建立不存在的上層目錄。

將多段位元組序列寫入檔案

連續寫入多個區塊時,請讓檔案保持開啟。每次呼叫 write() 都會從目前位置繼續寫入;若在迴圈內使用 wb 開啟檔案,先前寫入的區塊就會被清除。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from pathlib import Path
from tempfile import TemporaryDirectory

chunks = [b"First line.\n", b"Second line.\n", b"Third line.\n"]

with TemporaryDirectory() as directory:
    path = Path(directory) / "chunks.bin"
    with open(path, "wb") as file:
        for chunk in chunks:
            file.write(chunk)

每個區塊都必須是類位元組資料,例如 bytes、bytearray 或適用的緩衝區檢視。你也可以將位元組序列的可疊代物件傳給 file.writelines(chunks)。雖然名稱是 writelines(),它不會加入分隔符號或換行;有需要時,請將它們包含在區塊中。使用迴圈也能在區塊陸續到達時逐一處理,不必先將整份資料合併到記憶體中。

將 bytearray 寫入檔案

bytes 物件不可變;bytearray 則可在儲存前修改個別位元組或延長序列。兩者都能直接寫入二進位檔案。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from pathlib import Path
from tempfile import TemporaryDirectory

payload = bytearray(b"Line 1\n")
payload.extend(b"Line 2\n")
payload[0] = ord("l")

with TemporaryDirectory() as directory:
    path = Path(directory) / "bytearray.bin"
    with open(path, "wb") as file:
        file.write(payload)

在此範例中,extend() 會加入另一段序列,而索引指定則會修改第一個位元組。寫入時不需要先呼叫 bytes(payload);只有在其他地方需要不可變值時,才需要轉換。之後修改 bytearray 不會自動更新檔案。

將 BytesIO 資料寫入檔案

BytesIO 來自 io 模組,可提供記憶體中的二進位串流。它的 getbuffer() 方法會傳回涵蓋整個底層緩衝區的檢視,不必將內容複製到另一個 bytes 物件。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from io import BytesIO
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "buffer.bin"
    with BytesIO() as buffer:
        buffer.write(b"Header\n")
        buffer.write(b"Payload\n")
        with buffer.getbuffer() as view:
            with open(path, "wb") as file:
                file.write(view)

無論串流目前位於什麼位置,這個檢視都涵蓋所有已緩衝的資料。檢視存在期間,BytesIO 物件不能調整大小或關閉;巢狀的內容管理器會先釋放檢視,再關閉串流。若需要 bytes 值,請使用 buffer.getvalue()。若要透過 buffer.read() 複製資料,且需要從開頭讀取,請先呼叫 buffer.seek(0)。

追加位元組或在指定位置寫入

使用 ab 開啟檔案,可在結尾追加位元組而不清除現有內容。如果目的地不存在,此模式也會建立檔案。若要覆寫現有檔案中特定位移處的位元組,請使用 r+b,並呼叫檔案物件的 seek() 方法。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "records.bin"
    with open(path, "wb") as file:
        file.write(b"ABCDEF")

    with open(path, "ab") as file:
        file.write(b"GH")

    with open(path, "r+b") as file:
        file.seek(2)
        file.write(b"xy")

seek(2) 會將位置移至從開頭算起的位元組位移 2。接下來的寫入會取代兩個位元組,不會插入資料,也不會移除後續內容。若較短的新資料寫入後需要移除尾端位元組,請明確呼叫 truncate()。

r+b 需要檔案已存在,且開啟時會保留其內容。w+b 可讀取及寫入,但會先清空檔案。請勿使用追加模式進行定位更新:寫入會被導向檔案結尾,而透過定位來改變這項行為並不具可攜性。單純將位置移到檔案結尾之外不會延長檔案;後續寫入才可能延長檔案,而間隙區域的儲存細節取決於檔案系統。

緩衝與大型寫入

使用 open() 開啟的一般二進位檔案預設會使用緩衝。像 buffering=8192 這樣的明確設定會指定緩衝區大小,但不保證寫入速度會更快,也不會強制每次作業系統寫入都恰好包含 8192 個位元組。

產生大量資料時,請重複寫入可重用的區塊,而不要先建立一個大型合併物件:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from pathlib import Path
from tempfile import TemporaryDirectory

chunk = b"record\n" * 1024

with TemporaryDirectory() as directory:
    path = Path(directory) / "large.bin"
    with open(path, "wb", buffering=8192) as file:
        for _ in range(100):
            file.write(chunk)

這個範例示範可選的緩衝設定,並重複使用同一個區塊。請依實際工作負載的測量結果選擇緩衝區大小;沒有放諸四海皆準的最佳值。關閉檔案時會排清 Python 緩衝區。提早呼叫 flush() 會將緩衝資料送到底層串流,但這本身不保證資料已持久寫入實體儲存裝置。

關於寫入位元組的常見問題

如何讀回位元組? 使用 rb 開啟檔案,並呼叫 file.read()。傳回值是 bytes。若檔案很大,請分批讀取固定大小的區塊,而不要一次載入全部內容。

可以在二進位模式中寫入文字嗎? 可以,但必須先編碼,例如使用 text.encode("utf-8") 或 bytes(text, "utf-8")。讀取端必須使用相同的編碼才能還原文字。如果只需要儲存文字,使用文字模式並設定 encoding="utf-8" 可能更清楚。

為什麼 write() 會回報 TypeError? 二進位串流需要類位元組資料。直接傳入 str、整數或清單都不符合此要求。請先編碼文字,或使用 bytes(values) 將整數位元組值序列轉換成位元組;每個整數都必須介於 0 到 255。

空的資料內容會怎樣? 寫入 b"" 不會寫入任何位元組,但若使用 wb 開啟現有檔案,檔案內容已先被清空。若要追加資料並保留原有內容,即使新的資料內容為空,也請使用 ab。

為什麼目的地檔案沒有建立? 請確認上層目錄存在,而且目前程序有權限在該處寫入。相對路徑會以程序目前的工作目錄為基準解析。在應用程式中處理相關的 OSError;即使檔案已成功開啟,寫入或關閉時仍可能失敗。