使用者: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
,文件流會這樣讀取文件信息:
- 指針
0026700A02
讀取到90
並發送給 Python。 - 指針變為
0026700A03
。 - 指針
0026700A03
讀取到4E
並發送給 Python。 - ...
從而,可以藉助文件流讀取到文件內容 90 4E 54 46 53 20 20 20 ...
。
寫入文件同樣是通過指針進行的。
依舊按上述例子,假如我們輸入的路徑轉換為指針 0026700A70
,並準備寫入 00 00 00 00 00 00 ...
文件流會這樣寫入信息:
- 指針
0026700A70
被寫入00
。 - 指針變為
0026700A71
。 - 指針
0026700A71
被寫入00
。 - ...
從而,可以藉助文件流寫入內容。
因為寫入和讀取都會改變指針,一個流要麼寫入文件、要麼讀取文件。它不可能自動地先讀取一部分內容,再從部分開頭開始覆寫;它也無法自動重新讀取已經讀取過的內容——因為流只包含一個指針。如果想要進行這些操作,程序員應該自己保留一份指針的複製(因為文件系統會自己轉換,所以保留文件路徑即可)。
在實際實現時,對硬盤內容進行零星的讀取和寫入都是相當耗時的。為此,文件流會帶有「緩衝區」。緩衝區位於內存中,從而讀取和寫入消耗的時間較少。
不支持緩衝區的流也被稱作是「原始流」。
我們依舊使用上面表格的例子:
指針為 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 文檔中,用 file object 或 file-like object 來稱呼這種對象。
Python 的流被分為文本流和字節流兩種。字節流和上一章節的示例是一致的。
文本流的原理和字節流是一致的——不過其讀取的單位從字節變成字符。比如:對於讀取 GBK 編碼的文本文件(GBK編碼中,一個字符要兩個字節),文本流會一次性讀兩個字節、轉換為字符、再更改指針。
如果使用 Windows 系統,Python 的文本流在讀取字符時,會把 Windows 平台的換行符 \r\n
轉換為 \n
進行讀取。而在寫入字符時又會把 \n
轉換為 \r\n
進行寫入。因此,在存儲字節時,哪怕正確地將字節轉換成字符、並用文本流的方式寫入,也可能會由於自動轉換而出錯。讀者不應該用文本流來處理比如 jpg 或 exe 等二進制文件的數據。
我們可以用 open 函數創建文件流。
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
其中,file 是要打開文件的路徑。
mode 設定文件流的模式。
buffering 為緩衝策略。
encoding 和 errors 應當僅在創建文本流時輸入,這兩個參數和字符的編碼方式有關,具體參見 字符串編解碼 。如果創建文本流,則 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 為
- 寫入時
- 如果 newline 為
None
,則寫入的任何'\n'
字符都將轉換為系統的默認行分隔符os.linesep
。 - 如果 newline 是空字符串或
'\n'
,則不進行翻譯。 - 如果 newline 是
'\r'
或'\r\n'
,則寫入的'\n'
字符將被轉換為 newline。
- 如果 newline 為
closefd 以及 opener 和文件名描述符有關,在一些系統上不適用,因此這裡不多贅述。
以下表格列出了所有可用的模式,以及其使用時的表現。
文本流 模式 |
字節流 模式 |
支持的 操作 |
指針位置 | 備註 |
---|---|---|---|---|
'r' | 'rb' | 讀 | 文件開頭 | 和「流」讀取文件章節的表現完全一致。 |
'w' | 'wb' | 寫 | 開頭末尾 | 打開時會清空文件內容,所以指針位置在開頭、也在 末尾。 |
'a' | 'ab' | 讀寫 | 文件末尾 | 創建文件。 如果文件已存在,則會把指針放在文件末尾。 |
'x' | 'xb' | 寫 | 開頭末尾 | 創建文件。 如果文件已存在,則會報錯 oserror 。
|
'r+' | 'r+b' | 讀寫 | 文件開頭 | 打開時不會清空文件內容。 無論讀取還是寫入都會移動指針。 寫入時,直接對對應位置的內容進行替換。 |
'w+' | 'w+b' | 讀寫 | 開頭末尾 | 打開時會清空文件內容。 因為指針一定在文件末尾,讀取時,不會讀取到任何 內容,也不會移動指針。 |
'a+' | 'a+b' | 讀寫 | 文件末尾 | 創建文件。 如果文件已存在,則會把指針放在文件末尾。 因為指針一定在文件末尾,讀取時,不會讀取到任何 內容,也不會移動指針。 |
'x+' | 'x+b' | 讀寫 | 開頭末尾 | 創建文件。 如果文件已存在,則會報錯 oserror 。因為指針一定在文件末尾,讀取時,不會讀取到任何 內容,也不會移動指針。 |
數值 | 模式 |
---|---|
-1 | 使用默認緩衝策略 |
0 | 關閉緩衝(僅字節流可用,此時創建的是原始流) |
1 | 行緩衝(僅文本流、寫入時可用) |
大於1 的整數 |
設置固定字節數的緩衝 (僅 'r+' 以外的模式可用)
|
buffering 參數用於設置緩衝策略。此參數應當傳入一個整數。
默認緩衝策略為:
- 字節流和一般的文本流使用固定大小的緩衝區,大小取決於設備、一般為 4096 B 或 8192 B。
- 「交互式」文本流使用行緩衝。交互式文本流的
isatty()
方法會返回True
。
以下內容講解了文件流的常用操作和屬性。所有的操作和屬性都可以在 Python io 模塊的文檔中,「類的層次結構」章節的幾個小結 中查詢到。
closed 如果流已經被關閉,則該屬性為 True
。
close() 此方法會情況讀取緩衝區、把寫入緩衝區的內容寫入到硬盤上,並關閉流。如果流已經處於關閉狀態,此方法會什麼都不做。被關閉的流無法再執行任何讀寫操作。如果在使用文件流時,未使用此方法就退出 Python 可能會導致出錯。比如內容沒有被實際寫入等。
為防止忘記關閉,可以用 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
。