使用者:Xyy23330121/Python/文件處理


在使用 Python 時,有時我們希望 Python 自動幫助處理一些文件。本頁面將講述如何用 Python 處理基本的文本文檔,以及如何進行擴展。

文件流 編輯

「文件流」是處理文件的基本工具。

簡化原理 編輯

為了更好地理解文件流如何操作文件,我們應當先對其原理有基本的了解:「流」就是指針。以下內容簡化了流的原理。

指針 編輯

如果讀者學習過 C 語言,對指針一定很熟悉。簡單來講,指針是指向內存位置的內容。以下是一部分磁盤信息的示例表格:

0 1 2 3 4 5 6 7 8 9 A B C D E F
0026700A0* EB 52 90 4E 54 46 53 20 20 20 20 00 02 08 00 00
0026700A1* 00 00 00 00 00 F8 00 00 3F 00 FF 00 00 38 13 00
0026700A2* 00 00 00 00 80 00 80 00 E5 EB BB 1D 00 00 00 00
0026700A3* 00 00 0C 00 00 00 00 00 02 00 00 00 00 00 00 00
0026700A4* F6 00 00 00 01 00 00 00 5B A2 8E 1C C7 8E 1C D6
0026700A5* 00 00 00 00 FA 33 C0 8E D0 BC 00 7C FB 68 C0 07
0026700A6* 1F 1E 68 66 00 CB 88 16 0E 00 66 81 3E 03 00 4E
0026700A7* 54 46 53 75 15 B4 41 BB AA 55 CD 13 72 0C 81 FB

指針就是確定計算機應該讀取哪部分字節的。按上述例子,如果指針為 0026700A02 ,則計算機會讀取到 90

在具體使用時,我們一般都使用文件路徑。這是因為文件系統會自動將路徑轉換為指針,從而讀取正確的文件信息。

讀取文件的方式 編輯

文件流通過改變自身的指針來讀取文件。

依舊按上述例子,假如我們輸入的路徑轉換為指針 0026700A02,文件流會這樣讀取文件信息:

  1. 指針 0026700A02 讀取到 90 並發送給 Python。
  2. 指針變為 0026700A03
  3. 指針 0026700A03 讀取到 4E 並發送給 Python。
  4. ...

從而,可以藉助文件流讀取到文件內容 90 4E 54 46 53 20 20 20 ...

寫入文件的方式 編輯

寫入文件同樣是通過指針進行的。

依舊按上述例子,假如我們輸入的路徑轉換為指針 0026700A70,並準備寫入 00 00 00 00 00 00 ... 文件流會這樣寫入信息:

  1. 指針 0026700A70 被寫入 00
  2. 指針變為 0026700A71
  3. 指針 0026700A71 被寫入 00
  4. ...

從而,可以藉助文件流寫入內容。

因為寫入和讀取都會改變指針,一個流要麼寫入文件、要麼讀取文件。它不可能自動地先讀取一部分內容,再從部分開頭開始覆寫;它也無法自動重新讀取已經讀取過的內容——因為流只包含一個指針。如果想要進行這些操作,程序員應該自己保留一份指針的複製(因為文件系統會自己轉換,所以保留文件路徑即可)。

緩衝區 編輯

在實際實現時,對硬盤內容進行零星的讀取和寫入都是相當耗時的。為此,文件流會帶有「緩衝區」。緩衝區位於內存中,從而讀取和寫入消耗的時間較少。

不支持緩衝區的流也被稱作是「原始流」。

讀取緩衝區 編輯

我們依舊使用上面表格的例子:

指針為 0026700A00 ,緩衝大小設置為 16 B (一般會更大)。流會事先讀取 16 B 的內容,即 0026700A00 ~ 0026700A0F的內容,並存儲到內存中。

內存中的情況 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 EB 52 90 4E 54 46 53 20 20 20 20 00 02 08 00 00

在使用時,流會先在內存中讀取。為此,需要一個內存指針。如果內存中的內容讀取完了,就釋放緩衝區,指針變為 0026700A10,並重新緩衝。

這樣,流就將零星的對磁盤的讀取,變成了大塊的對磁盤的讀取以及零星的對內存的讀取。顯著提高了讀取速度。

寫入緩衝區 編輯

寫入緩衝區與讀取緩衝區類似,它同樣是將小塊需寫入的信息在內存中整合成大塊,再一次性寫入磁盤的工具。

Python 的流 編輯

Python 的流有一些特殊的功能。在 Python 文檔中,用 file object 或 file-like object 來稱呼這種對象。

文本流和字節流 編輯

Python 的流被分為文本流和字節流兩種。字節流和上一章節的示例是一致的。

文本流的原理和字節流是一致的——不過其讀取的單位從字節變成字符。比如:對於讀取 GBK 編碼的文本文件(GBK編碼中,一個字符要兩個字節),文本流會一次性讀兩個字節、轉換為字符、再更改指針。

如果使用 Windows 系統,Python 的文本流在讀取字符時,會把 Windows 平台的換行符 \r\n 轉換為 \n 進行讀取。而在寫入字符時又會把 \n 轉換為 \r\n 進行寫入。因此,讀者不應該用文本流來處理比如 jpg 或 exe 等二進制文件的數據。

創建文件流 編輯

關於 mode 的測試
這個 頁面 展示了對其中一些模式的測試、以及測試結果。

我們可以用 open 函數創建文件流。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

其中,file 是要打開文件的路徑

mode 設定文件流的模式。

buffering 為緩衝策略。

encodingerrors 應當僅在創建文本流時輸入,這兩個參數和字符的編碼方式有關,具體參見 字符串編解碼 。如果創建文本流,則 encoding 默認為 locale.getencoding() [1]的結果,而 errors 為 'strict'

newline 也僅在文本流中有用,它決定如何解析來自流的換行符。

  • 讀取時
    • 如果 newline 為 None,則把 '\n''\r''\r\n' 視為換行符,並翻譯成 '\n'
    • 如果 newline 為空字符串,則把 '\n''\r''\r\n' 視為換行符,但不進行翻譯。
    • 如果 newline 為 '\n', '\r''\r\n',則僅把 newline 視為換行符。
  • 寫入時
    • 如果 newline 為 None,則寫入的任何 '\n' 字符都將轉換為系統的默認行分隔符 os.linesep
    • 如果 newline 是空字符串或 '\n',則不進行翻譯。
    • 如果 newline 是'\r''\r\n',則寫入的 '\n' 字符將被轉換為 newline。

closefd 以及 opener 和文件名描述符有關,在一些系統上不適用,因此這裡不多贅述。

mode 編輯

以下表格列出了所有可用的模式,以及其使用時的表現。

文本流
模式
字節流
模式
支持的
操作
指針位置 備註
'r' 'rb' 文件開頭 「流」讀取文件章節的表現完全一致。
'w' 'wb' 開頭末尾 打開時會清空文件內容,所以指針位置在開頭、也在
末尾。
'a' 'ab' 讀寫 文件末尾 創建文件。
如果文件已存在,則會把指針放在文件末尾。
'x' 'xb' 開頭末尾 創建文件。
如果文件已存在,則會報錯oserror
'r+' 'r+b' 讀寫 文件開頭 打開時不會清空文件內容。
無論讀取還是寫入都會移動指針。
寫入時,直接對對應位置的內容進行替換。
'w+' 'w+b' 讀寫 開頭末尾 打開時會清空文件內容。
因為指針一定在文件末尾,讀取時,不會讀取到任何
內容,也不會移動指針。
'a+' 'a+b' 讀寫 文件末尾 創建文件。
如果文件已存在,則會把指針放在文件末尾。
因為指針一定在文件末尾,讀取時,不會讀取到任何
內容,也不會移動指針。
'x+' 'x+b' 讀寫 開頭末尾 創建文件。
如果文件已存在,則會報錯oserror
因為指針一定在文件末尾,讀取時,不會讀取到任何
內容,也不會移動指針。

buffering 編輯

buffering
數值 模式
-1 使用默認緩衝策略
0 關閉緩衝(僅字節流可用,此時創建的是原始流)
1 行緩衝(僅文本流、寫入時可用)
大於1
的整數
設置固定字節數的緩衝
(僅 'r+' 以外的模式可用)

buffering 參數用於設置緩衝策略。此參數應當傳入一個整數。

默認緩衝策略為:

  • 字節流和一般的文本流使用固定大小的緩衝區,大小取決於設備、一般為 4096 B 或 8192 B。
  • 「交互式」文本流使用行緩衝。交互式文本流的 isatty() 方法會返回 True

文件流的實例方法與屬性 編輯

以下內容講解了文件流的常用操作和屬性。所有的操作和屬性都可以在 Python io 模塊的文檔中,「類的層次結構」章節的幾個小結 中查詢到。

關閉 編輯

closed 如果流已經被關閉,則該屬性為 True

close() 此方法會情況讀取緩衝區、把寫入緩衝區的內容寫入到硬盤上,並關閉流。如果流已經處於關閉狀態,此方法會什麼都不做。被關閉的流無法再執行任何讀寫操作。如果在使用文件流時,未使用此方法就退出 Python 可能會導致出錯。比如內容沒有被實際寫入等。

為防止忘記關閉,可以用 with 語句來創建文件流。具體參見下面的章節。

with 語句與文件流 編輯

Python 的文件流本身也是一個上下文管理器。其 __enter__ 方法會返回其自身,而 __exit__ 方法會調用 close() 方法。於是,可以這樣創建文件流並使用:

with open(file) as text:
    print(text.read())

讀取 編輯

read(size=-1, /)

  • 對於字節流,從對象中讀取 size 個字節並返回。如果 size 為 -1,則讀取所有字節,直到文件結束。
  • 對於文本流,從對象中讀取 size 個字符並返回。如果 size 為 -1,則讀取所有字符,直到文件結束。

readinto(b, /)

  • 對於字節流,該項會把字節數據寫入 bytearray 對象 b 中,並返回讀取的字節數。

readline(size=-1, /) 讀取一行的內容。如果指定了 size ,則最多讀取 size 個字節。
對於字節流而言,行結束符為 b'\n';對於文本流而言,則按照創建文件流時使用的 newline 參數來決定。

readlines(hint=-1, /) 讀取內容,並按行分割為列表。列表中的元素是讀取到的每一行的內容(包含換行符)。如果讀取完一行時,讀取的總字符數(文本流) / 字節數(字節流)超出 hint,則不會繼續讀取下一行。特別的,對文件流進行迭代操作 for line in file: ... 時,等同於使用 for line in file.readlines(): ...

readable() 如果流可以讀取,則該方法會返回 True

寫入 編輯

write(b, /)

  • 對於字節流,將給定的 bytes 或 bytearray 寫入到文件中,並返回寫入的字節數。
  • 對於文本流,將給定的字符串寫入到文件中,並返回寫入的字符數。

writelines(lines, /) 將列表lines中的內容寫入到流中,不會添加行分隔符。

writeable() 如果流可以寫入,則該方法會返回 True

刷新緩衝區 編輯

flush() 把寫入緩存區中所有內容寫到磁盤上。這對於只讀的流不起作用。

指針 編輯

seek(offset, whence=os.SEEK_SET, /) 將指針修改到對應的字節。其中offset 為相對於 whence 的字節數,而 whence 可以為以下值:

數值 os 模塊中的常量名 注釋
0 os.SEEK_SET 文件開頭位置(默認)
1 os.SEEK_CUR 當前指針位置
2 os.SEEK_END 文件末尾

某些操作系統還支持其他的值。

tell() 返回當前指針位置。

seekable() 如果流支持上述指針操作,返回 True

參考文獻 編輯