如何使用 Python 檔案物件讀取及寫入檔案?以及檔案指標,關閉檔案,取得檔案物件資訊等問題

閱讀 29:02·字數 8714·更新 
Youtube 頻道
訂閱 133

本節主要講述如何使用 Python 檔案物件讀取及寫入檔案,至於如何開啟檔案,以及編碼,換行,緩沖等問題,請參考如何使用 Python open 函式開啟檔案?以及編碼,解碼,換行,緩沖等問題一節。

Python 檔案物件

Python 檔案物件負責檔案的讀取,寫入,關閉等操作,通常作為內建函式open的傳回值,根據所傳遞的參數的不同,open函式傳回的 Python 檔案物件可能是以下型別之一,TextIOWrapper(文字檔案),BufferedReader(擁有緩沖區的唯讀二進位檔案),BufferedWriter(擁有緩沖區的唯寫二進位檔案),BufferedRandom(擁有緩沖區的可讀取及寫入的二進位檔案),FileIO(停用緩沖區的二進位檔案)。

使用 Python 檔案物件讀取檔案中的字元或位元組

當 Python 檔案物件允許讀取時,可以使用read方法讀取本文檔案中的字元或二進位檔案中的位元組,在讀取的同時,檔案指標將被移動。如果是讀取字元,那麽read方法傳回 Pythonstr字串,如果是讀取位元組,那麽read方法傳回 Pythonbytes位元組序列(你可以將其視為另類的“字串”)。

read(size=-1)

size 參數

size參數表示能夠讀取的最大字元數(文字檔案)或最大位元組數(二進位檔案),如果該參數為None或小於0,那麽將讀取從目前指標位置到檔案末尾的所有字元或位元組。

這裏需要指出,英文字母和漢字均被視為一個字元。

對於停用緩沖區的二進位檔案,Python 檔案物件的readall方法可以讀取從目前指標位置到檔案末尾的所有字元或位元組,該函式的執行效果與呼叫read方法但不給出size參數的效果類似。

readall()

開啟檔案

關於如何在 Pythonopen函式中設定緩沖策略,請檢視為 Python open 函式指定緩沖策略一段。

在下面的範例中,通過read方法讀取的前 4 個位元組,包括“你”字的 3 個位元組,以及“好”字的第一個位元組。

read.py
# 請將命令列跳躍至腳本檔案 read.py 所在的目錄,然後執行他
# 讀取檔案中的字元
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'讀取 4 個字元:{file.read(4)}')
	print(f'讀取剩余字元:{file.read()}')

# 讀取檔案中的位元組 with open('read.txt', 'rb') as file: print(f'讀取 4 個位元組:{file.read(4)}') print(f'讀取剩余位元組:{file.read()}')
# 讀取檔案中的位元組,但停用緩沖區 with open('read.txt', 'rb', buffering=0) as file: print(f'讀取 4 個位元組:{file.read(4)}') print(f'讀取剩余位元組:{file.readall()}')
read.txt
你好,world!
Yes,很好
讀取 4 個字元:你好,w
讀取剩余字元:orld!
Yes,很好
讀取 4 個位元組:b'\xe4\xbd\xa0\xe5'
讀取剩余位元組:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'
讀取 4 個位元組:b'\xe4\xbd\xa0\xe5'
讀取剩余位元組:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'

當指標位於檔案末尾時,Python 檔案物件的 read 方法將傳回空字串或空位元組序列

當檔案指標位於檔案末尾時,呼叫 Python 檔案物件的read方法將傳回空字串''(文字檔案)或空位元組序列b''(二進位檔案),而不是空值None

read_eof.py
# 請將命令列跳躍至腳本檔案 read_eof.py 所在的目錄,然後執行他
with open('read.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'從檔案末尾讀取字元:{file.read()}')

with open('read.txt', 'rb') as file: file.read() print(f'從檔案末尾讀取位元組:{file.read()}')
從檔案末尾讀取字元:
從檔案末尾讀取位元組:b''

使用 Python 檔案物件讀取檔案中的行

當 Python 檔案物件允許讀取時,可使用readline方法讀取從目前指標位置到行末尾或檔案末尾的所有字元或位元組(包括換行字元和歸位字元,如果是文字檔案,那麽由 Pythonopen函式的newline參數決定),並可以指定能夠讀取的最大字元數或最大位元組數。

Python 檔案物件的readline方法的傳回值是字串或bytes位元組序列,在讀取字元的同時,檔案指標將被移動。

readline(size=-1)

size 參數

size參數為能夠讀取的最大字元數(文字檔案)或最大位元組數(二進位檔案),預設為-1,表示沒有最大字元數或最大位元組數的限製。

開啟檔案

關於 Pythonopen函式的newline參數,你可以檢視為 Python open 函式指定換行₁的處理方式一段。

在下面的範例中,由於我們分別使用read方法讀取了 4 個字元和 4 個位元組,因此,接下來的readline方法會從第 5 個字元和第 5 個位元組開始讀取。

readline.py
# 請將命令列跳躍至腳本檔案 readline.py 所在的目錄,然後執行他
# 讀取文字檔案中的行
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'讀取 4 個字元:{file.read(4)}')
	# 包括 \n
	print(f'讀取行:{file.readline()}')
	print(f'讀取行(最多 2 個字元):{file.readline(2)}')
	print(f'讀取行:{file.readline()}')

# 讀取二進位檔案中的行 with open('read.txt', 'rb') as file: print(f'讀取 4 個位元組:{file.read(4)}') # 包括 \r\n print(f'讀取行:{file.readline()}') print(f'讀取行(最多 2 個位元組):{file.readline(2)}') print(f'讀取行:{file.readline()}')
讀取 4 個字元:你好,w
讀取行:orld!

讀取行(最多 2 個字元):Ye
讀取行:s,很好
讀取 4 個位元組:b'\xe4\xbd\xa0\xe5'
讀取行:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\n'
讀取行(最多 2 個位元組):b'Ye'
讀取行:b's\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'

當指標位於檔案末尾時,Python 檔案物件的 readline 方法將傳回空字串或空位元組序列

當檔案指標位於檔案末尾時,呼叫 Python 檔案物件的readline方法將傳回空字串''(文字檔案)或空位元組序列b''(二進位檔案),而不是空值None

readline_eof.py
# 請將命令列跳躍至腳本檔案 readline_eof.py 所在的目錄,然後執行他
with open('read.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'從檔案末尾讀取一行:{file.readline()}')

with open('read.txt', 'rb') as file: file.read() print(f'從檔案末尾讀取一行:{file.readline()}')
從檔案末尾讀取一行:
從檔案末尾讀取一行:b''

當 Python 檔案物件允許讀取時,可使用readlines方法讀取從目前指標位置到檔案末尾的所有行(包括換行字元和歸位字元,如果是文字檔案,由 Pythonopen函式的newline參數決定),並可以指定能夠讀取的最大字元數或最大位元組數,在讀取的同時,檔案指標將被移動。

Python 檔案物件的readlines方法的傳回值是一個串列物件,串列中的元素對應從檔案讀取到的行(一個字串或位元組序列)。

readlines(hint=-1)

hint 參數

hint參數為能夠讀取的最大字元數(文字檔案)或最大位元組數(二進位檔案),預設為-1,表示沒有最大字元數或最大位元組數的限製。

需要說明的是,即便已經超出了最大字元數或最大位元組數,Python 檔案物件的readlines方法依然會讀取至行的末尾或檔案末尾。

在下面的範例中,我們分別使用readlines方法讀取了 12 個字元和 12 個位元組,檔案指標將到達第二行和留在第一行,對於這兩種情況,Python 檔案物件均會讀取至行末尾。

readlines.py
# 請將命令列跳躍至腳本檔案 readlines.py 所在的目錄,然後執行他
# 讀取文字檔案中的多個行
with open('readlines.txt', 'r', encoding='utf8') as file:
	print(f'讀取多個行(最多 12 個字元):{file.readlines(12)}')
	print(f'讀取剩余的行:{file.readlines()}')

# 讀取二進位檔案中的多個行 with open('readlines.txt', 'rb') as file: print(f'讀取多個行(最多 12 個位元組):{file.readlines(12)}') print(f'讀取剩余的行:{file.readlines()}')
readlines.txt
你好,world!
Yes,很好
What a day!
讀取多個行(最多 12 個字元):['你好,world!\n', 'Yes,很好\n']
讀取剩余的行:['What a day!']
讀取多個行(最多 12 個位元組):[b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\n']
讀取剩余的行:[b'Yes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd\r\n', b'What a day!']

當指標位於檔案末尾時,Python 檔案物件的 readlines 方法將傳回空的串列

當檔案指標位於檔案末尾時,呼叫 Python 檔案物件的readlines方法將傳回空的串列[],而不是空值None

readlines_eof.py
# 請將命令列跳躍至腳本檔案 readlines_eof.py 所在的目錄,然後執行他
with open('readlines.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'從檔案末尾讀取多個行:{file.readlines()}')

with open('readlines.txt', 'rb') as file: file.read() print(f'從檔案末尾讀取多個行:{file.readlines()}')
從檔案末尾讀取多個行:[]
從檔案末尾讀取多個行:[]

通過周遊 Python 檔案物件讀取檔案中的行

除了使用方法readlinereadlines來讀取檔案中的行,你還可以通過周遊 Python 檔案物件的方式來讀取行,因為檔案物件也是疊代器物件。

for.py
# 請將命令列跳躍至腳本檔案 for.py 所在的目錄,然後執行他
# 使用 for 陳述式周遊檔案
with open('readlines.txt', 'r', encoding='utf8') as file:
	for l in file:
		print(l)
你好,world!

Yes,很好

What a day!

使用 Python 檔案物件將二進位檔案中的位元組讀取至類位元組序列物件

對於二進位檔案,你可以使用 Python 檔案的readinto方法,將檔案中的位元組讀取至類位元組序列(bytes-like)物件。readinto方法會嘗試讀取足夠多的位元組,以填入類位元組序列物件,並將實際讀取的位元組數作為傳回值(int型別)。

readinto(b)

b 參數

b參數是一個可以寫入的類位元組序列物件。

在下面的範例中,我們嘗試將二進位檔案中剩余的 23 個位元組讀取至bytearray,因此bytearray的最後 7 個位元組為\x00

readinto.py
# 請將命令列跳躍至腳本檔案 readinto.py 所在的目錄,然後執行他
# 讀取位元組至 bytearray
with open('read.txt', 'rb') as file:
	ba8 = bytearray(8)
	# 嘗試讀取 8 個位元組以填入 ba
	print(f'讀取了 {file.readinto(ba8)} 個位元組')
	print(ba8)

ba30 = bytearray(30) # 讀取剩余的 23 個位元組 print(f'讀取了 {file.readinto(ba30)} 個位元組') print(ba30)
讀取了 8 個位元組
bytearray(b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc')
讀取了 23 個位元組
bytearray(b'\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd\x00\x00\x00\x00\x00\x00\x00')

使用 Python 檔案物件將字元或位元組寫入檔案

當 Python 檔案物件允許寫入時,可使用write方法將字元或位元組寫入本文檔案或二進位檔案,並傳回寫入的字元數或位元組數。

write(s|b)

s,b 參數

s參數為需要寫入的字串,b參數為包含需要寫入的位元組的類位元組序列物件。

utf8編碼中的大部分漢字占用 3 個位元組,字串'第一行\n第二行'在解碼後是一個包含 19 個位元組的bytes物件。

write.py
# 請將命令列跳躍至腳本檔案 write.py 所在的目錄,然後執行他
# 將字元寫入檔案
with open('write.txt', 'w', encoding='utf8') as file:
	c = file.write('第一行\n第二行')
	print(f'寫入 {c} 個字元')

# 將位元組寫入檔案 with open('writeb.txt', 'wb') as file: bs = '第一行\n第二行'.encode() c = file.write(bs) print(f'寫入 {c} 個位元組')
寫入 7 個字元
寫入 19 個位元組

使用 Python 檔案物件將行寫入檔案

當 Python 檔案物件允許寫入時,可以使用writelines方法將多行資料(字元或位元組)寫入本文檔案或二進位檔案。

writelines(lines)

lines 參數

lines參數是一個疊代器物件,疊代器中的元素是需要寫入的字串,或包含需要寫入的位元組的類位元組序列(bytes-like)物件。

Python 檔案物件的 writelines 方法不會自動為每一行新增換行字元或歸位字元

Python 檔案物件的writelines方法並不會自動為每一行新增換行字元或歸位字元(或者換行字元或歸位字元對應的位元組),因此需要開發人員自行處理。

在下面的範例中,我們自行為第一行新增換行字元和其對應的位元組。

writelines.py
# 請將命令列跳躍至腳本檔案 writelines.py 所在的目錄,然後執行他
# 將多個行寫入檔案
with open('writelines.txt', 'w', encoding='utf8') as file:
	file.writelines(('第一行\n', '第二行'))

# 將多個行寫入檔案 with open('writelinesb.txt', 'wb') as file: file.writelines(('第一行\n'.encode(), '第二行'.encode()))

使用 Python 檔案物件將緩沖區中的內容寫入檔案

對於支援寫入操作並使用了緩沖區的 Python 檔案物件,其方法flush可用於立即將緩沖區中的內容寫入檔案,當然,很多情況下,呼叫該方法是沒有必要的。

對於不支援寫入操作或停用了緩沖區的 Python 檔案物件,呼叫flush方法不會產生任何效果。

flush()

在下面的範例中,由於緩沖區的大小為4b'abc'會保留在緩沖區中,因為他們僅占用 3 個位元組。

flush.py
# 請將命令列跳躍至腳本檔案 flush.py 所在的目錄,然後執行他
with open('flush.txt', 'wb', buffering=4) as file:
	file.write(b'abc')
	# 將緩沖區中的 abc 寫入檔案
	file.flush()

使用 Python 檔案物件取得和設定檔案指標的位置

在絕大多數情況下,檔案指標的位置是 Python 檔案物件從檔案讀取內容或將內容寫入檔案的開始位置,並且會在讀取或寫入內容後發生變化,以方便下一次的讀取和寫入。在開啟檔案之後,檔案指標的位置可能在檔案開頭或末尾。

無論是文字檔案還是二進位檔案,檔案指標的位置總是以位元組為單位,因此,對於文字檔案對應的 Python 檔案物件,需要得知字元對應的位元組數才能正確的移動檔案指標,否則可能導致錯誤的發生。

Python 檔案物件提供了名稱為tell的方法,用於獲得目前檔案指標的位置,該方法傳回一個整數,如果傳回值為0,則表示指標位於檔案的開頭。

tell()

Python 檔案物件提供了名稱為seek的方法,用於改變檔案指標的位置。

seek(offset, whence)

offset 參數

offset參數是一個表示檔案指標位移值的整數,當offset大於0時,檔案指標將向檔案末尾移動指定的位元組數,當offset小於0時,檔案指標將向檔案開頭移動指定的位元組數。

whence 參數

whence參數是一個整數,如果為0(預設值,os.SEEK_SET),則從檔案的開頭位置計算offset表示的指標位移值,如果為1os.SEEK_CUR),則從目前檔案指標位置計算offset表示的指標位移值,如果為2os.SEEK_END),則從檔案的末尾位置計算offset表示的指標位移值。

在下面的範例中,我們開啟了文字檔案read.txt,因此要移動檔案指標的位置,需要知道字元所占用的位元組數,這裏檔案開頭處的“你好”兩字占用了 6 個位元組。

pointer.py
# 請將命令列跳躍至腳本檔案 pointer.py 所在的目錄,然後執行他
file = open('read.txt', 'r', encoding='utf8')

# 開始時指標位於檔案開頭 print(f'指標位置:{file.tell()}') # 將指標移動到“你好”之後,“你好”占用了 6 個位元組 file.seek(6, 0) # 讀取下一個字元“,” print(file.read(1)) # 由於“,”占用 3 個位元組,因此指標位置現在為 9 print(f'指標位置:{file.tell()}')
指標位置:0

指標位置:9

Python 檔案物件不能將檔案指標的位置設定為負數

無論如何,Python 檔案物件無法將檔案指標的位置設定為負數,否則將導致例外狀況。不過,使檔案指標的位置超出檔案末尾並沒有問題。

在下面的範例中,我們將檔案指標移動到檔案末尾後 100 的位置,但之後的read方法並沒有擲回例外狀況。

pointer_err.py
# 請將命令列跳躍至腳本檔案 pointer_err.py 所在的目錄,然後執行他
file = open('read.txt', 'r+b')

# 檔案指標位置將超出檔案末尾 file.seek(100, 2) print(f'指標位置:{file.tell()}') # 讀取檔案不會產生例外狀況 print(file.read())
# ERROR 檔案指標的位置不能是負數 file.seek(-100, 0)
指標位置:131
b''
OSError: [Errno ] Invalid argument

判斷 Python 檔案物件是否支援隨機存取

當 Python 檔案物件不支援檔案的隨機存取時,你將不能取得或設定檔案指標的位置,即不能呼叫檔案物件的tellseek方法,否則會引發例外狀況OSError

通過 Python 檔案物件的seekable方法,可以判斷檔案物件是否支援隨機存取,如果該方法傳回True,那麽表示支援隨機存取。

seekable()

判斷 Python 檔案物件是否可以讀取或寫入

Python 檔案物件的readablewritable方法傳回一個布林值,用於表示檔案物件是否支援讀取和寫入操作(主要由 Pythonopen函式的mode參數決定),傳回True表示支援。

readable()
writable()

開啟檔案

關於 Pythonopen函式的mode參數,你可以檢視為 Python open 函式指定檔案開啟模式一段。

info.py
# 請將命令列跳躍至腳本檔案 info.py 所在的目錄,然後執行他
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'讀取:{file.readable()},寫入:{file.writable()}')

with open('read.txt', 'r+b') as file: print(f'讀取:{file.readable()},寫入:{file.writable()}')
讀取:True,寫入:False
讀取:True,寫入:True

使用 Python 檔案物件關閉檔案

Python 檔案物件擁有一個名稱為close的方法,用於關閉檔案物件對應的檔案,一旦該方法被呼叫,你將無法通過檔案物件對檔案實施操作,比如,讀取和寫入。

close()

確保在程式碼中呼叫 Python 檔案物件的 close 方法

雖然 Python 檔案物件會在終結時嘗試關閉檔案,但在程式碼中明確的呼叫 Python 檔案物件的close方法,依然是保險的做法,否則通過檔案物件寫入的內容存在無法儲存至檔案的可能。

如果使用 Pythonwith陳述式和open函式開啟檔案,那麽不需要呼叫檔案物件的close方法,因為,該方法會被檔案物件的__exit__方法直接或間接呼叫。

with 陳述式

關於 Python 的with陳述式,你可以檢視Python with 陳述式,with 陳述式內容管理器介紹一節。

在下面的範例中,由於檔案物件已經呼叫了close方法,因此,再次通過檔案物件寫入資料將導致例外狀況。

close.py
# 請將命令列跳躍至腳本檔案 close.py 所在的目錄,然後執行他
file = open('close.txt', 'w', encoding='utf8')
file.write('寫入一些資料!')
# 關閉檔案
file.close()

# ERROR 檔案已經關閉 file.write('再寫入一些資料?')
ValueError: I/O operation on closed file.

判斷 Python 檔案物件對應的檔案是否已經關閉

通過 Python 檔案物件的closed屬性,可以檢查檔案物件對應的檔案是否已經關閉,如果closed屬性為True,那麽表明檔案已經關閉。

closed

在下面的範例中,我們通過with陳述式開啟檔案,在with陳述式結束時,檔案物件的__exit__方法將被呼叫,而__exit__方法將呼叫close方法關閉檔案。

closed.py
# 請將命令列跳躍至腳本檔案 closed.py 所在的目錄,然後執行他
# with 陳述式結束之後,檔案將被關閉
with open('close.txt', 'w', encoding='utf8') as file:
	file.write('寫入一些資料!')

# 檢查檔案是否被關閉 if file.closed: print('檔案 close.txt 已經關閉')
檔案 close.txt 已經關閉

使用 Python 檔案物件截斷檔案

對於支援寫入的 Python 檔案物件,可以使用truncate方法將檔案截斷,並傳回截斷後的大小,該方法不會改變檔案指標的位置。

truncate(size=None)

size 參數

size參數是一個整數(大於等於0),表示在第幾個位元組處截斷檔案。如果該參數被忽略或者為None,則在目前檔案指標的位置截斷檔案(註意,實際效果可能並非如此)。

事實上,size參數所指示的位置可以超出檔案末尾,這會導致檔案的末尾被填入數個位元組\x00

在下面的範例中,“真是美好”占用了 12 個位元組,呼叫方法truncate之後檔案指標沒有發生變化。

truncate.py
# 請將命令列跳躍至腳本檔案 truncate.py 所在的目錄,然後執行他
with open('truncate.txt', 'r+', encoding="utf8") as file:
	# 顯示檔案內容
	print(file.read())
	# 保留“真是美好”
	print(f'從第 12 個位元組處截斷:{file.truncate(12)}')
	# 檔案指標位置不會被 truncate 方法改變
	print(f'目前指標位置:{file.tell()}')
truncate.txt
真是美好的一天
真是美好的一天
從第 12 個位元組處截斷:12
目前指標位置:21

取得 Python 檔案物件的其他資訊

在使用 Python 內建函式open建立檔案物件時,我們會使用一些參數來設定檔案物件的某些功能,而檔案物件則提供了以下變數,屬性,方法來說明他們。

Python 檔案物件的name屬性是檔案路徑或檔案描述器(file descriptor)對應的整數,這與傳遞給open函式的file參數有關。

Python 檔案物件的fileno方法傳回檔案描述器對應的整數,是的,即便你向open函式傳遞檔案路徑,但最終使用的是檔案描述器。

Python 檔案物件的mode屬性表示開啟模式,這與傳遞給open函式的mode參數有關。

name
fileno()
mode

對於文字檔案,Python 檔案物件的encoding變數表示編碼格式,errors變數表示編碼或解碼錯誤的處理方式,他們均與傳遞給open函式的同名參數有關。

對於文字檔案,Python 檔案物件的line_buffering屬性是一個布林值,表示是否使用了行緩沖,如果為True,那麽表示使用了行緩沖。

encoding
errors
line_buffering

對於停用緩沖區的二進位檔案,Python 檔案物件的closefd屬性表示當檔案關閉時是否同時關閉檔案描述器,這與傳遞給open函式的closefd參數有關。

closefd

info.py
# …
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'name {file.name}')
	print(f'fileno {file.fileno()}')
	print(f'mode {file.mode}')
	print(f'encoding {file.encoding}')
	print(f'errors {file.errors}')
	print(f'line_buffering {file.line_buffering}')

import os fd = os.open('read.txt', os.O_RDONLY | os.O_BINARY) with open(fd, 'rb', buffering=0, closefd=False) as file: print(f'name {file.name}') print(f'closefd {file.closefd}')
name read.txt
fileno 3
mode r
encoding utf8
errors strict
line_buffering False
name 3
fileno 3
closefd False

程式碼

src/zh-hant/file_system/file_handling/file_objects·codebeatme/python·GitHub