如何使用 Python 檔案物件讀取及寫入檔案?以及檔案指標,關閉檔案,取得檔案物件資訊等問題
本節主要講述如何使用 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 所在的目錄,然後執行他
# 讀取檔案中的字元
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()}')
你好,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 所在的目錄,然後執行他
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 所在的目錄,然後執行他
# 讀取文字檔案中的行
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 所在的目錄,然後執行他
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 所在的目錄,然後執行他
# 讀取文字檔案中的多個行
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()}')
你好,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 所在的目錄,然後執行他
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 檔案物件讀取檔案中的行
除了使用方法readline
和readlines
來讀取檔案中的行,你還可以通過周遊 Python 檔案物件的方式來讀取行,因為檔案物件也是疊代器物件。
# 請將命令列跳躍至腳本檔案 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 所在的目錄,然後執行他
# 讀取位元組至 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 所在的目錄,然後執行他
# 將字元寫入檔案
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 所在的目錄,然後執行他
# 將多個行寫入檔案
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()
在下面的範例中,由於緩沖區的大小為4
,b'abc'
會保留在緩沖區中,因為他們僅占用 3 個位元組。
# 請將命令列跳躍至腳本檔案 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
表示的指標位移值,如果為1
(os.SEEK_CUR
),則從目前檔案指標位置計算offset
表示的指標位移值,如果為2
(os.SEEK_END
),則從檔案的末尾位置計算offset
表示的指標位移值。
在下面的範例中,我們開啟了文字檔案read.txt
,因此要移動檔案指標的位置,需要知道字元所占用的位元組數,這裏檔案開頭處的“你好”兩字占用了 6 個位元組。
# 請將命令列跳躍至腳本檔案 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 所在的目錄,然後執行他
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 檔案物件不支援檔案的隨機存取時,你將不能取得或設定檔案指標的位置,即不能呼叫檔案物件的tell
和seek
方法,否則會引發例外狀況OSError
。
通過 Python 檔案物件的seekable
方法,可以判斷檔案物件是否支援隨機存取,如果該方法傳回True
,那麽表示支援隨機存取。
seekable()
判斷 Python 檔案物件是否可以讀取或寫入
Python 檔案物件的readable
和writable
方法傳回一個布林值,用於表示檔案物件是否支援讀取和寫入操作(主要由 Pythonopen
函式的mode
參數決定),傳回True
表示支援。
readable()
writable()
開啟檔案
關於 Pythonopen
函式的mode
參數,你可以檢視為 Python open 函式指定檔案開啟模式一段。
# 請將命令列跳躍至腳本檔案 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 所在的目錄,然後執行他
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 所在的目錄,然後執行他
# 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 所在的目錄,然後執行他
with open('truncate.txt', 'r+', encoding="utf8") as file:
# 顯示檔案內容
print(file.read())
# 保留“真是美好”
print(f'從第 12 個位元組處截斷:{file.truncate(12)}')
# 檔案指標位置不會被 truncate 方法改變
print(f'目前指標位置:{file.tell()}')
真是美好的一天
真是美好的一天
從第 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
# …
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