使用者: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

參考文獻[編輯 | 編輯原始碼]