Python IO檔案管理

小小垂髫發表於2022-03-19

檔案操作

我們可以使用python來操作檔案,比如讀取檔案內容、寫入新的內容等,因為任何計算機檔案的本質都是一些有不同字尾的字元組成的。

python檔案操作的兩種模式

開啟模式

  1. while,寫入模式,簡寫為 w ,指定的檔案不存在則建立檔案,存在則開啟並清空內容,並且將檔案指標(游標)放在檔案的開頭。
  2. read,讀取模式,簡寫為 r ,檔案不存在則報錯,存在則開啟檔案,並且將檔案指標放在檔案的開頭。
  3. append,追加模式,簡寫為 a ,檔案不存在則建立檔案,存在則開啟檔案,並且將指標放在檔案末尾。
  4. xor,異或模式,簡寫為 x ,檔案存在則報錯,不存在則建立檔案,將檔案指標放在檔案的開頭。

擴充套件模式

擴充套件模式是用來配合開啟模式的輔助模式,擴充套件模式單獨不能使用。

  1. plus,增強模式,簡寫為 + ,可以讓開啟模同時具有讀寫功能。
  2. bytes,bytes模式,簡寫為 b ,將檔案按照二進位制位元組流編碼進行讀寫。

因此我們根據這兩種大的模式可以組合成為16種操作檔案的方法。

模式 作用 模式 作用
w 寫入模式,只可寫,不可讀。 a 追加模式,只可寫,不可讀。
w+ 寫入模式,可寫可讀。 a+ 追加模式,可寫可讀。
wb 寫入模式,按照二進位制位元組流編碼可寫不可讀 ab 追加模式,按照二進位制位元組流可寫不可讀
wb+ 寫入模式,按照二進位制位元組流編碼可寫可讀 ab+ 追加模式,按照二進位制位元組流可寫可讀。
r 讀取模式,只可讀,不可寫。(預設模式) x 異或模式,只可寫,不可讀。
r+ 讀取模式,可寫可讀。 x+ 異或模式,可寫可讀。
rb 讀取模式,按照二進位制位元組流編碼可讀不可寫。 xb 異或模式,二進位制位元組流可寫不可讀。
rb+ 讀取模式,按照二進位制位元組流編碼可讀可寫。 xb+ 異或模式,二進位制位元組流可寫可讀。

異或模式和寫入模式的區別在於,異或模式如果開啟的檔案在指定的路徑中如果存在,就會報錯;而寫入模式是直接開啟不會報錯,但是會將原始檔中的所有內容清空。因為寫入模式和讀取模式之間的互相配合,異或模式的使用頻率越來越少,正在逐步淘汰當中。

編碼格式的瞭解

編碼是資訊從一種形式或格式轉換為另一種形式的過程,就是用預先規定的方法將文字、數字或其它物件編成數碼,或將資訊、資料轉換成規定的電脈衝訊號。這樣做的目的是為了簡化資訊之間的傳遞。但是為保證編碼的正確性,編碼要規範化、標準化,即需有標準的編碼格式。常見的編碼格式有ASCII、ANSI、GBK、GB2312、Unicode、UTF-8等。

所有的編碼格式,都是將字元轉換成對應的二進位制格式。將西方的字母文字和數字按照一個位元組的方式儲存,而將亞洲中中、日、朝等文字按照多位元組儲存。這是因為西方的字母語言,字母的數量遠少於東方的文字數量,因此程式設計工作中一般更加的傾向與儘量多的使用英文的原因,因為相對的來說使用漢字等字元較少的程式可以佔據更少的系統資源。

常用的編碼格式

英文原始編碼:ASCII碼

ACSII編碼只有128個字元,26個英文字母的大小寫之外,還有一些常用的符號,還有一些不可或缺的系統控制字元等。ACSII編碼中沒有除了英文字母之外的其它語言字元。

在這裡插入圖片描述

中文國家標準編碼:GB系列編碼

凡是由GB開頭的編碼集都是屬於中國國家的標準編碼字符集,只是不同的版本而已,使用這個編碼的漢字佔用的系統資源最少,中文使用2個位元組的儲存空間。比如GB2312。

萬國碼:Unicode編碼

Unicode編碼包含世界上所有的文字,無論什麼字元都以4個位元組進行儲存。這是Unicode編碼的缺點,雖然擁有世界上最齊全的字元,但是佔用的系統資源很大,所以很少使用。

因此在這個基礎之上改進,建立了可變長的Unicode編碼集,UTF系列。這是目前世界上最主流的編碼字符集,在這個編碼集當中,不用擔心任何字元會亂碼,字母文字和數字使用一個位元組的儲存空間,中文等字元使用三個位元組的儲存空間,大大節省了空間的佔用。比如UTF-8。

open函式的使用

python中操作檔案要使用到open函式,open函式的作用是用於開啟一個檔案,建立一個file物件,使用相關的方法呼叫它對檔案進行讀寫操作。

語法:open(file, mode=None, encoding=None)

引數說明:

  1. file:檔案的位置和名稱
  2. mode:操作的模式,使用簡寫,就是我們上述的16中操作方式
  3. encoding:指定編碼型別,比如UTF-8、GB2312、ACSII等

open函式指定這些資訊之後,返回一個TextIOWrapper物件,使用這個物件,我們可以按照指定的操作模式和編碼格式來操作我們指定的檔案。

檔案的寫入(寫入模式)

現在我們在使用open函式建立一個檔案,並寫入內容。

在這裡插入圖片描述

可以看到我們當前的目錄當中只有一個main.py檔案,我們現在寫入程式碼。

# 指定檔案的位置,要使用字串,可以使用絕對路徑和相對路徑
# 操作模式的選擇,我們要建立一個新的檔案並寫入內容,使用 w
# 指定編碼格式為UTF-8,這是最常使用的編碼格式

# fp就是檔案的IO物件,問價控制程式碼,用來操作檔案
# i --- >   input  輸入
# o --- >   output 輸出
fp = open('test.txt', 'w', encoding='UTF-8')

# 使用write函式寫入內容
fp.write('Hello motherland')

# 使用close函式關閉檔案
fp.close()

執行python程式碼之後,我們發現在原來的目錄下面多出了一個名為test.txt的檔案。

在這裡插入圖片描述

開啟這個檔案我們就會發現,檔案中的內容就是我們寫下的內容。

在這裡插入圖片描述

現在我們重新使用 w 模式開啟這個檔案,但是不操作任何東西,讓我們看看結果如何。

fp = open('test.txt', 'w', encoding='UTF-8')
fp.close()

在這裡插入圖片描述

沒錯,這個檔案中的內容被清空了,這就是w模式的如果檔案存在,就開啟檔案並清空。

檔案的讀取(讀取模式)

我們現在執行下面的程式碼,使用 r 模式讀取檔案中的內容。

# 使用 r 模式開啟msr.txt檔案
fp = open('msr.txt', 'r', encoding='UTF-8')

# 讀取檔案中的內容
res = fp.read()
print(res)

# 關閉檔案
fp.close()

發現程式報錯了,這是為什麼?因為使用 r 模式如果指定的檔案不存在就會報錯。

那我們先建立一個msr.txt檔案在重新讀取一下。

# 先建立一個msr.txt檔案
fp = open('msr.txt', 'w', encoding='UTF-8')
# 寫入內容
fp.write('劉德華太帥了。')
# 關閉檔案
fp.close()


# 然後重新讀取這個檔案
fp = open('msr.txt', 'r', encoding='UTF-8')

# 讀取檔案中的內容
res = fp.read()

# 列印讀取的內容
print(res)   # 劉德華太帥了。

# 關閉檔案
fp.close()

不再報錯了,而且也成功的列印出來檔案中的內容。

檔案內容追加(追加模式)

追加模式如果檔案不存在就建立檔案,反之就開啟檔案,但是可寫入模式的不同之處就在於,追加模式開啟檔案不會清空檔案中的原有的資料內容。

開啟msr.txt檔案,我們看到只有一行文字。

在這裡插入圖片描述

現在我們執行下面的程式碼

# 使用追加模式開啟檔案
fp = open('msr.txt', 'a', encoding='UTF-8')

# 在檔案中寫入內容
fp.write('但是劉德華沒有博主帥。')

# 關閉檔案
fp.close()

開啟檔案我們看到,原有的資料並沒有被清空掉,並且寫入了新的內容。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-monzJF4R-1647692650673)(在這裡插入圖片描述
)]

位元組流的轉換

bytes是用來傳輸或者是儲存的資料格式,如果是在檔案的操作過程中按照bytes的模式操作的話,就需要將資料格式轉換成為bytes流才可以。

二進位制的位元組流就是底層的程式碼。

使用 b 字首

在字串之前加上字元 b 代表是二進位制的位元組流,但是範圍只是ASCII編碼,也就是說這樣並不支援中文。

# 在字串之前加上 b 字首
bytechar = b'hello motherland'

print(bytechar)  # b'hello motherland'
# 該字串的資料型別就變成了bytes流
print(type(bytechar))   # <class 'bytes'>

bytechar = b'你好祖國'	# error,只能將ACSII編碼中的字元變成bytes流
使用函式

使用encode函式和decode函式可以將字串在普通字串和位元組流的形式中來回的轉換,而且可以將所有的字元變成bytes位元組流,因為預設使用UTF-8編碼,當然你也可以指定轉換的編碼格式。

函式 作用
encode 將字串轉換為二進位制的位元組流
decode 將二進位制的位元組流轉換為字串

語法:

string.encode(encoding='UTF-8')

bytes.decode(encoing='UTF-8')

var = '我的祖國'

# 將字串變成位元組流,預設使用UTF-8編碼
res = var.encode()
print(res)  # b'\xe6\x88\x91\xe7\x9a\x84\xe7\xa5\x96\xe5\x9b\xbd'
print(type(res))    # <class 'bytes'>


# 指定編碼格式UTF-8
res = var.encode(encoding='UTF-8')
print(res)  # b'\xe6\x88\x91\xe7\x9a\x84\xe7\xa5\x96\xe5\x9b\xbd'
# 可以看到指定為UTF-8編碼的和預設的結果是一樣的


# 指定編碼格式為ASCII
res = var.encode('ASCII')	# error
# 因為原字串是中文,所以不能使用ASCII編碼


# 指定為GBK編碼
res = var.encode('GBK')
print(res)  # b'\xce\xd2\xb5\xc4\xd7\xe6\xb9\xfa'
# 可以看到GBK的編碼中文可以節省更多的空間


# 可以使用 len 函式檢測位元組流的長度
print(len(res))	# 8


# 解碼
var = res.decode()  # error
# 應為預設使用的UTF-8解碼,但是res的編碼格式是GBK,所以失敗


# 只能使用對應的編碼格式解碼
var = res.decode('GBK')
print(var)  # 我的祖國

儲存二進位制的位元組流

如果在操作檔案的時候要使用位元組流的方式,在使用open函式選擇模式的時候要加上 b ,表示進行位元組流的操作,然後open函式就不能在指定編碼格式了,因為現在的操作的都是位元組流,然而位元組流本身就已經是指定的編碼格式編碼過了。

# 如果指定了位元組流模式還要指定encoding引數就會報錯
fp = open('test.txt', 'wb', encoding='UTF-8')   # error


# 使用位元組流模式只能進行位元組流的寫入
fp = open('test.txt', 'wb')
# fp.write('hello motherland')    # error,不能直接使用字串
fp.write(b'hello motherland')
fp.write('我的祖國'.encode())
fp.close()

寫在檔案中的內容還是原來的樣子,不是位元組流的形式

在這裡插入圖片描述

注意事項:
  1. 使用位元組流模式編輯過的檔案只能使用位元組流模式去操作
  2. 使用什麼格式的位元組流寫入檔案的內容,讀取的時候只能使用對應的編碼格式去解碼
  3. 任何檔案都可以使用位元組流模式去讀取內容,讀取的內容是位元組流,如果這個檔案是按照某個編碼格式寫入的,解碼需要使用對應的編碼格式;如果這個檔案的內容不是使用位元組流模式寫入,讀取的位元組流預設是UTF-8格式的。

上下文管理器

在python中,有一些任務是當你開啟之後,結束的時候需要專門的關閉任務,比如檔案操作,在結束操作的使用需要使用close()函式專門的關閉檔案、結束任務,這樣就很繁瑣,所以python中推出了 with …… as ……的語法,在 with 程式碼塊中如果結束操作,不需要在專門的結束任務。

上下文管理器,任何需要進行上下文操作的物件,都可以使用此語法。

語法:with 任務 as 操作控制程式碼:

# 不需要在使用close()函式專門的關閉檔案,結束任務了
with open('test.txt', 'wb') as fp:
	fp.write(b'hello motherland')

重新整理緩衝區

我們學習了這麼久,每次都一定要關閉檔案、結束任務,這樣做的意義是什麼?

比較直觀的目的就是為了儲存檔案,但是好奇的我們早就測試了不使用close()關閉檔案,寫入的內容一樣是儲存了檔案中的,這是怎麼回事?

看下面的程式碼,發現我們檔案中依然是儲存了我們寫入的內容。

fp = open('test.txt', 'w', encoding='UTF-8')
fp.write('我和我的祖國,就像是海和浪花一朵。')

在這裡插入圖片描述

這是因為,關閉檔案的根本目的是為了重新整理緩衝區,然而重新整理緩衝區的方法不止一種。

  1. 當檔案關閉的時候自動重新整理緩衝區
  2. 當整個程式執行結束的時候自動重新整理緩衝區
  3. 當緩衝區寫滿還自動重新整理緩衝區
  4. 手動重新整理緩衝區

重新整理緩衝區的意義在於最後的儲存檔案,就好像在使用文件編輯器的時候,雖然寫滿內容,但最後不點選儲存按鈕內容也不會儲存下來。

而我們上面的例子就是因為程式執行結束的時候自動重新整理了緩衝區,所以才儲存了寫入檔案的內容,而close的作用就是關閉檔案,關閉檔案也可以重新整理緩衝區,所以這就是每次要關閉檔案的原因所在,為了防止自動重新整理的失敗。

那麼什麼情況之下程式就沒有辦法執行完呢?

比如說程式的意外中斷、或者是死迴圈,下面的程式碼中就是因為死迴圈的原因導致程式沒有辦法執行完成,而沒有儲存新寫入的內容。

下面的程式碼,先是寫入了內容,然後就是一個死迴圈,這樣程式永遠都不會執行完成,就不能自動的重新整理緩衝區,如果程式意外中斷,內容也不會寫入檔案當中,你可以將程式執行起來之後,強制中斷測試一下,會發現是一個空檔案。

with open('test.txt', 'w', encoding='UTF-8') as fp:
	fp.write('我和我的祖國,一刻也不能分割。')
	while True:
		pass
手動重新整理

上面的例子中,檔案沒有辦法關閉,程式沒有辦法執行完成,貌似緩衝區也很難寫滿,難道我們的內容就沒有辦法儲存了嗎?

你機智的寫上了一行程式碼,是close()函式,這樣就關閉了檔案,就可以將死迴圈之前的內容儲存了嘛。

with open('test.txt', 'w', encoding='UTF-8') as fp:
	fp.write('我和我的祖國,一刻也不能分割。')
	fp.close()	# 關閉檔案
	while True:
		pass

你經過測試,上面的程式碼的確的儲存了寫入的內容,但是我們關閉了檔案,再次操作檔案的時候就必須重新開啟檔案,不然沒有辦法繼續操作。

with open('test.txt', 'w', encoding='UTF-8') as fp:
	fp.write('我和我的祖國,一刻也不能分割。')
	fp.close()
	fp.write('我和我的祖國,就像是海和浪花一朵。')   # error,檔案已經關閉
	while True:
		pass

發現寫入的第二條內容根本就沒法執行了,怎麼辦?使用fiush()函式手動重新整理緩衝區。

with open('test.txt', 'w', encoding='UTF-8') as fp:
	fp.write('我和我的祖國,一刻也不能分割。')
	fp.flush()
	fp.write('我和我的祖國,就像是海和浪花一朵。')
    fp.flush()
	while True:
		pass

發現手動重新整理將內容儲存了下來,而且沒有影響程式的執行。以後如果程式任務過大,沒有執行完成就意外中斷,這樣就有一點資料儲存不下來的風險,我們就可以隔著一段任務手動重新整理一下,就不至於將所有的資料全部丟失。

檔案的擴充套件模式

我們經過上面的學習,用到了寫、讀、手動重新整理、關閉檔案等幾種操作檔案的函式,但是除此之外,還有一些常用的相關函式。

函式 作用
write 寫入資料
read 讀取資料
fiush 手動重新整理緩衝區
close 關閉檔案
seek 調整指標(游標)的位置
tell 返回當前指標左側所有的位元組數
readable 判斷檔案物件是否可讀
writeable 判斷檔案物件是否可寫
readline 讀取檔案的一行內容
readlines 將檔案中的內容按照換行讀取到列表當中
writelines 將內容是字串的可迭代資料寫入檔案當中
truncate 把要擷取的字串提取出來,然後清空內容並將擷取的內容重新寫入

read的使用

plus增強模式的使用

在open函式中,使用 + 號,進入增強模式,可讀可寫。

我們現在使用 r+ 模式開啟之前的檔案,讀取其中的內容。

with open('test.txt', 'r+', encoding='UTF_8') as fp:
	# 讀取內容
	res = fp.read()
	print(res)  # 我和我的祖國,一刻也不能分割。我和我的祖國,就像是海和浪花一朵。
	
	# 可以指定字元的個數,讀取指定個數的字元
	res = fp.read(5)
	print(res)  #

發現什麼第二遍沒有讀取出任何的內容,我們重新開啟一遍檔案,重新讀取。

with open('test.txt', 'r+', encoding='UTF_8') as fp:
	# 讀取五個字元
	res = fp.read(5)
	print(res)  # 我和我的祖

	# 再讀取五個字元
	res = fp.read(5)
	print(res)  # 國,一刻也

發現第二遍讀取的內容是接著第一遍讀取的內容之後的,我們重新開啟一遍檔案,寫一些內容。

with open('test.txt', 'r+', encoding='UTF_8') as fp:
	# 寫入內容
	fp.write('我永遠和我的祖國在一起')
	# 讀取其中的內容
	res = fp.read()
	print(res)  # 能分割。我和我的祖國,就像是海和浪花一朵。

讀取內容的時候,發現沒有我們寫入的內容,而且讀取的檔案內容怎麼看起來好怪異的感覺啊,怎麼少了些內容?

我們重新開啟檔案讀取一遍

with open('test.txt', 'r+', encoding='UTF_8') as fp:
	res = fp.read()
	print(res)  # 我永遠和我的祖國在一起能分割。我和我的祖國,就像是海和浪花一朵。

為什麼我們的寫入的內容在檔案的開頭,而且還替換掉了原有的一部分資料?我們上面的一系列操作為什麼那麼的奇怪?

這都是因為游標的作用在做怪。

游標的作用

還記得我們之前介紹四種開啟模式的時候嗎?寫入模式游標在文件最後,讀取模式游標在文件最前,追加模式游標在文件最前,異或模式游標在文件最前。

寫入的內容和讀取的內容都是從游標的位置開始的。

read()函式預設讀取游標一右側所有的內容。而不是文件中的所有內容,之前的測試之所以可以一次性的讀取出所有的內容是因為我們開啟文件使用的是讀取模式,游標的位置在文件的開頭。游標會隨著讀取的內容而移動,讀取到哪個字元游標就移動到哪個字元的後面。

write()寫入內容的時候是覆蓋模式。我們都知道我們的計算機系統中的文字輸入方式是有兩種的,使用insert鍵就可以切換著兩種模式。一種是插入模式,一種是覆蓋模式。

插入模式是我們平常最經常使用的,比如說我們開啟一個文字編輯軟體,隨便的寫入一段內容,然後把游標移動到文件的開頭,寫入內容,發現新的內容是插入到了舊的內容之前的,舊的內容不會消失,而是後移,這就是插入模式;然後重新將游標移動到文件的開頭,然後按下insert鍵,這個時候你的輸入方式就變成了覆蓋模式,現在的你每當輸入一個新的字元就會覆蓋掉後面的一箇舊字元,這就是覆蓋模式,python的文字編輯就是這種覆蓋模式。游標隨著寫入的內容向後移動。

游標位置的移動

我們剛才的時候就瞭解到了有一個可以調整游標位置的函式,叫做seek,使用這個函式我們可以隨意的調節游標的位置,從而編輯檔案的時候可以更加的隨心所欲。

seek(offset: int, [whence: int = 0])
seek(偏移量, [基準位置])

seek函式的兩個引數都是整型。

第一個參數列示的是偏移量,單獨使用時表示將游標移動到從文件的開頭算起的第N個位元組的位置後;
第二個參數列示的游標的位置,使用的時候只有0、1、2三個選型,且偏移量必須為0;
0代表的是文件的最開端
1代表的是游標的當前位置
2代表的是文件的最後端

# 先使用 w+ 模式開啟一個檔案,這個時候的檔案為空,游標在文件的開頭位置
with open('test.txt', 'w+', encoding='UTF-8') as fp:
	
	# 我們寫入內容,這個時候游標的位置隨著寫入的內容到了文件的最後
	fp.write('hello motherland.')
	
	# 所以現在的游標的右側沒有任何一個位元組符,所以讀不出任何的內容
	res = fp.read()
	print(repr(res))    # ''
	
	# 所以游標還是在最後的位置,使用seek切換游標的位置為開頭,讀取剛才寫入的內容
	fp.seek(0)
	res = fp.read()
	print(repr(res))    # 'hello motherland.'
	
	# 讀取完內容之後,游標又到了文件的最後的位置,調整到開頭的第五個位元組符的位置
	fp.seek(5)
	
	# 再次讀取檔案的內容,這一次只讀取5個字元,發現前五個字元沒有了
	res = fp.read(5)
	print(repr(res))    # ' moth'
	
	# 現在游標在文件的第十個字元位置,我們將游標切換到文件的最後,然後讀取文件發現什麼內容也沒有
	fp.seek(0, 2)
	res = fp.read()
	print(repr(res))    # ''

注意到了嗎?我說的seek移動的是位元組的數量,什麼是位元組的數量?

我們之前說的不同的編碼格式對於不同的字元都是不一樣的,但是所有的編碼格式對於英文字母為主的一些的字元都是一個位元組的大小,但是漢字不一樣,漢字在GB中是兩個位元組、UTF中是三個位元組、Unicode中是四個位元組。

seek的偏移單位是位元組,不是字元,所以在使用seek在操作bytes位元組流時,要注意移動的間隔,因為移動的是位元組位數,而在GB編碼中一個漢字兩個位元組,在Unicode(UTF-8)中,一個漢字三個位元組,如果seek將指標移動至漢字之間,就會導致讀取時漢字的編碼不完整而導致錯誤。

# 重新寫入一個檔案,注意我們的編碼格式
with open('test.txt', 'w+', encoding='UTF-8') as fp:

	fp.write('我和我的祖國,一刻也不能分割。我和我的祖國,就像是海和浪花一朵。')

	# 我們現在讀取除了第一句話之後的內容,前面的內容一共是15個字元,我們使用seek跳過去
	fp.seek(15)
	res = fp.read()
	print(repr(res))    # '國,一刻也不能分割。我和我的祖國,就像是海和浪花一朵。'

	# 咦?怎麼只跳過了五個漢字?因為我們使用的UTF-8的編碼,一個漢字由3個位元組,真好是15個單位

	# 你是幸運的,如果我們在右移一個位元組的單位,就是一個漢字都沒有完全遷移完會怎麼樣?
	fp.seek(1)
	# res = fp.read()
	# print(res)      # error, 報錯了,因為剩下的字元不是完整的,所以沒有辦法讀出,就報錯了
# 就像是你好的UTF-8編碼是六個位元組組成的,
print('你好'.encode())    # b'\xe4\xbd\xa0\xe5\xa5\xbd'

# 如果去掉了一個位元組,就是不完整的了,還能解碼出來嗎?
print(b'\xbd\xa0\xe5\xa5\xbd'.decode()) # error,解碼失敗

所以在使用seek函式的時候一定要慎用。

tell的使用

# tell 當前游標左側所有的位元組數(返回位元組數)

# 使用閱讀模式開啟檔案
with open('test.txt', 'r+', encoding='UTF-8') as fp:

   # 使用tell函式檢視貫標左側的位元組數
   res = fp.tell()
   print(res)  # 0

   # 因為閱讀模式的游標在檔案的開頭,所以返回0個位元組數

   # 使用seek將游標移動到文件的末尾
   fp.seek(0, 2)

   # 使用tell檢視整個文件的位元組數,這就是文件的大小
   res = fp.tell()
   print(res)  # 96

   # 快去看看你的檔案資訊中的檔案大小是不是96位元組的?

其它的相關函式

判斷檔案物件可讀可寫
# 使用 r+ 模式開啟檔案
with open('test.txt', 'r+', encoding='UTF-8') as fp:
   # 使用readable 和 readable 檢視這個文件是否可讀可寫
   if fp.readable():
      print('本文件可讀')
   if fp.writable():
      print('本文件可寫')
'''
結果:
本文件可讀
本文件可寫
'''
readline

讀取一行內容

# 開啟檔案,重新寫入多行內容
with open('test.txt', 'w+', encoding='UTF-8') as fp:
   # 可以使用多行字串
   fp.write('''11111
22222
33333
''')

   # 也可以使用轉義字元進行換行
   fp.write('44444\n55555\n66666')

在這裡插入圖片描述

with open('test.txt', 'r+', encoding='UTF-8') as fp:

   # 使用read讀取的整個文件的內容
   res = fp.read()
   print(res)
   '''
   結果:
   11111
   22222
   33333
   44444
   55555
   66666
   '''

   # 使用readline讀取一一行的內容
   fp.seek(0)
   res = fp.readline()
   print(res)  # 11111

   # 再讀取一行
   res = fp.readline()
   print(res)  # 22222

   # 可以指定讀取的字元個數
   res = fp.readline(3)
   print(res)  # 333

   # 如果指定的個數大於本行的字元個數,就讀取本行所有的內容
   res = fp.readline(1000)
   print(res)  # 33
   
   # 為什麼是33不是44444?因為readline也要受到游標的影響
readlines

將檔案中的內容按照換行讀取到列表中

with open('test.txt', 'r+', encoding='UTF-8') as fp:
	res = fp.readlines()
	print(res)  # ['11111\n', '22222\n', '33333\n', '44444\n', '55555\n', '66666'

注意:readlines不會影響游標的移動,但是讀取的是游標的右側資料;而且readlines的讀取將換行符也讀取上了,因為換行符本身也是一行的內容。

按行讀取內容我一般使用到readlines函式,但是也可以使用其它的方法,比如直接遍歷open例項化物件,open例項化物件本身就是一個可迭代物件,它將檔案中的內容按照換行符分開。

with open('text.txt', 'r', encoding='UTF-8') as fp:
    for line in fp:
        print(line)
writelines

將內容是字串的可迭代性資料寫入檔案中,writelines不會根據元素換行。

lst = ['china', 'america', 'russia']

with open('test.txt', 'w+', encoding='UTF-8') as fp:
    # 使用writelines寫入內容
    fp.writelines(lst)
	fp.seek(0)
    # 讀取資料
	res = fp.readlines()
	print(res)	# ['chinaamericarussia']
truncate

檔案中的內容只保留擷取的內容。

從檔案開頭開始,擷取指定位元組長度的內容,然後將檔案清空,然後將擷取的內容重新填入檔案中。

# 開啟一個檔案
with open('test.txt', 'w+', encoding='UTF-8') as fp:

	# 寫入一段內容
	fp.write('1234567890')

	# 保留擷取的內容,只保留前5個位元組的內容
	fp.truncate(5)

	# 檢視檔案的內容
	fp.seek(0)
	res = fp.read()
	print(res)  # 12345

關於生成檔案MD5心得

我在工作時需要給呼叫翻譯狗的一個API,用於上傳文獻並翻譯返回,但是對方需要檔案MD5進行驗證,我們需要在接入介面的時候,需要將檔案md5傳入,這個時候就出現了一些問題,我在傳入檔案和檔案MD5的時候,被對方回應檔案MD5不匹配,我很好奇,為什麼會出現這樣的情況?

我在使用這個介面當中,有好幾處地方比如token的生成和檔案md5的地方都會需要md5加密,所以為此我們專門將生成md5的程式碼封裝成為一個函式(將字串輸入,返回md5,程式碼如下:

import hashlib

def enMD5(target):
    """ MD5加密 """
    res = hashlib.md5(target.encode()).hexdigest()
    return res

python中生成md5需要輸入位元組流格式的資料,而我一開始只有字串的資料需要使用md5加密,所以我在函式中將字串變成位元組流。token就是傳入字串得到的。

但是檔案md5的話可以直接讀出位元組流的格式,但是因為再使用這個函式不方便,所以我使用正常讀取文件的方式讀取檔案中的內容,然後放入函式中,結果就是上面說的,和對方得出的檔案md5並不匹配。我自認為我的程式碼是沒有問題的,於是我們依次查詢問題的所在,後來我發現網上很多博主的方法都是直接從檔案讀取二進位制位元組流的方式獲取的,我實在是沒有辦法了,就想會不會就是讀取方式的問題呢?果然,我就發現不同格式讀取的出來的結果是不同的,測試的案例如下:

with open(file_path, 'w', encoding='UTF-8') as fp:
    fp.write('msr\nhello\r\nmotherland.')

with open(file_path, 'rb') as fp:
    print(fp.read())

with open(file_path, 'r', encoding='UTF-8') as fp:
    print(r'f', repr(fp.read()), sep='')

上述的結果為:

b'msr\r\nhello\r\r\nmotherland.'
f'msr\nhello\n\nmotherland.'

沒錯,我發現直接使用b模式和普通模式讀取內容然後轉化成為bytes的結果是不同的,那麼也必將導致最後檔案md5是不正確的。大家也看到了,不管是哪一種讀取的方法其實和我寫入的內容都是不同的,在本次的測試案例當中對於換行有著不同的認知,讀取的原因我沒有深入瞭解,但是我注意到了官方文件中說b模式就是專門讀取檔案位元組流格式的,所以以後大家生成檔案md5的時候,一定要直接使用b模式讀取檔案內容。

上述的測試環境是:
python: python3.6.8_win_x64(Cpython)
system: windows_10_x64

相關文章