Python基礎知識之檔案的讀取操作

格瑞姆瑞坡發表於2019-02-16

讀取檔案的操作步驟

有一道腦筋急轉彎,問把大象裝進冰箱的步驟,答案很簡單,開啟冰箱、把大象推進去、關閉冰箱。這就是一個處理問題的思路,我們對檔案的操作和這個一樣,第一步:開啟檔案;第二部:處理檔案(讀取或者寫入);第三部關閉檔案,怎麼樣?其實很簡單吧,下面我們就來詳細說說檔案的操作。

開啟檔案

open(file, mode=`r`, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

開啟檔案要使用open()函式,()內涉及的引數在初期階段我們只需要知道三個引數即可,那就是file、mode、和encoding

file

首先是檔名,型別是字串,要包含路徑,如果要開啟的檔案和當前檔案在同一個目錄下則可以省略路徑(相對路徑).

mode

然後是開啟檔案的模式,格式是:mode=`模式`, 這裡的”mode=”可以省略,直接寫模式即可,如果不指定模式則預設為r,具體的模式如下:
這裡引入一個偽名詞——指標,想象一下當你在word中編輯文字時有個游標吧?你可以把指標想象成那個游標,游標在哪裡你對檔案的所有操作就從哪裡開始。

r 
只讀。檔案的初始指標在檔案的開頭。這是預設模式。
rb
只讀的二進位制格式。檔案的初始指標在檔案的開頭。
r+
讀寫。檔案的初始指標在檔案的開頭。
rb+
讀寫的二進位制格式。檔案的初始指標在檔案的開頭。
w
只寫。如果該檔案已存在則開啟檔案,清空檔案內容。如果該檔案不存在,則建立新檔案。
wb
只寫的二進位制格式。如果該檔案已存在則開啟檔案,清空檔案內容。如果該檔案不存在,建立新檔案。
w+
寫讀。如果該檔案已存在則開啟檔案,清空檔案內容。如果該檔案不存在,建立新檔案。
wb+
寫讀的二進位制格式。如果該檔案已存在則開啟檔案,清空檔案內容。如果該檔案不存在,建立新檔案。
a
追加寫。如果該檔案存在,檔案的初始指標在檔案的結尾。新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。
ab
追加寫的二進位制格式。如果該檔案存在,檔案的初始指標在檔案的結尾。新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。
a+
追加寫讀。如果該檔案已存在,檔案的初始指標在檔案的結尾。檔案開啟時會是追加模式。如果該檔案不存在,建立新檔案用於讀寫。
ab+
追加寫讀的二進位制格式。如果該檔案已存在,檔案的初始指標在檔案的結尾。如果該檔案不存在,建立新檔案用於讀寫。

encoding

第三個引數是檔案的編碼(非二進位制格式時使用),編碼的設定必須與要開啟的檔案的編碼一致,否則會報錯,也就是說原始檔是用什麼編碼寫入的,你開啟時就得用什麼編碼開啟。

處理檔案


我們來舉一個出錯的例子,要實現的操作是開啟檔名為“test”的檔案,原檔案編碼為utf-8,而我們開啟時使用了gbk編碼。
檔案中只有一句話:十步殺一人,千里不留行。
程式碼如下:

file = open(`test`,`r`,encoding=`gbk`)
content = file.read()
print(content)
file.close()

這裡說明以下,open函式返回的結果是一個檔案物件,所以要用一個變數接收一下(file),
這樣我們就可以方便地使用這個物件的方法了,比如file.read(),file.write,file.flush()等等
read()是檔案物件的一個方法,作用是讀取指定的字元數,如果未指定則預設讀取所有內容
但是上面的程式碼執行後會報錯,下面是報錯資訊,大意是gbk編碼無法解碼(檔案開啟的過程實際就是解碼的過程)

    ---------------------------------------------------------------------------

    UnicodeDecodeError                        Traceback (most recent call last)

    <ipython-input-78-bb879f008680> in <module>()
          4 #這裡說明以下,open函式返回的結果是一個檔案物件,所以要用一個變數接收一下(file),這樣我們就可以方便地使用這個
          5 #物件的方法了,比如file.read(),file.write,file.flush()等等
    ----> 6 content = file.read()#read()是檔案物件的一個方法,作用是讀取指定的字元數,如果未指定則預設讀取所有內容
          7 print(content)
          8 file.close()

    UnicodeDecodeError: `gbk` codec can`t decode byte 0xad in position 4: illegal multibyte sequence

解決的辦法很簡單,把編碼方式改為utf-8即可,因為linux系統預設是utf-8編碼,所以編碼也可以省略不寫,又因為我們只是要讀取下檔案,所以模式為r,而預設模式為r,也可以省略不寫。
所以程式碼就變成了下面的樣子:

file = open(`test`)
content = file.read()
print(content)
file.close()
輸出為:
十步殺一人,千里不留行

方法read()中也可以新增數字引數,作用是指定讀取的字元數,不管是漢字還是字母一個字元對應一個字母或者漢字,這裡一定要和位元組區分開,例子如下:

file = open(`test`)
content = file.read(3)
print(content)
file.close()
輸出為:
十步殺

我們之前使用的開啟檔案的方式比較麻煩,因為必須要在結束時寫關閉檔案的語句,這裡介紹一種簡便的方法,格式是:

with open() as file_name:
    操作程式碼......

使用這種方法的好處是不用寫關閉檔案的語句,with 和as是關鍵字,記住格式即可.
接下來我們建立一個二進位制格式的檔案

with open(`二進位制`,`wb`) as file: 
    file.write(`十步殺一人,千里不留行`.encode(`utf8`))

因為我們開啟檔案的方式是以二進位制格式開啟的,那麼在寫入檔案時就要寫入二進位制格式的字串,但是對於”十步殺一人,千里不留行“這個字串的二進位制格式是什麼呢?我們並不知道,所以我們使用.encode(utf8)來把這個字串轉換成二進位制格式。實際上我們通過另外一段程式碼是可以知道這個字串對應的二進位制格式的,程式碼如下:

print(`十步殺一人,千里不留行`.encode(`utf8`))
輸出的二進位制格式(表現形式為16進位制,為什麼?因為二進位制太長了啊!!!)為:
b`xe5x8dx81xe6xadxa5xe6x9dx80xe4xb8x80xe4xbaxbaxefxbcx8cxe5x8dx83xe9x87x8cxe4xb8x8dxe7x95x99xe8xa1x8c`

在實際中我們當然不可能寫這麼長的東西,所以我們使用.encode(`utf8`)來轉化。轉化時我們使用了utf-8的編碼方式。
在這裡說明一下為什麼要編碼,想弄清楚這件事就要明白計算機的儲存原理。
簡單地講,檔案在計算機中都是以二進位制格式儲存的,對於計算機來講只有兩個數字有意義,那就是0和1,也就是二進位制,而不同的0和1的組合代表不同的含義(編碼不同),比如在gbk編碼下一個漢字由兩個位元組表示,也就是16位二進位制數(16個0和1的組合),而在utf-8編碼下一個漢字由3個位元組表示,也就是24位二進位制數(24個0和1的組合),而我們現在要給計算機儲存的就是0和1的組合,那麼在讀取檔案或者轉化字串時,如果你不告訴計算機你使用的是哪種編碼的話,計算機怎麼可能知道這些0和1的組合代表什麼含義呢?所以從計算機取出資料或者把普通字串轉化成二進位制格式時你必須告訴計算機你使用的是什麼編碼方式。
在本例中使用utf-8編碼,所以在字串後面加上.encode(utf8)

那麼為什麼平時可以直接寫入普通字串呢?那是因為在用open()函式開啟檔案的時候就已經指定了編碼方式。
形象地解釋:open函式相當於開啟了一種通道,平時開啟時都是用某種編碼的方式開啟的,所以我們在寫入內容時不必管它以什麼編碼進入通道的(因為在open函式裡面就已經指定了編碼方式)
而當open函式使用二進位制格式開啟時(就是帶b的模式),這個通道就沒有指定編碼方式,通道里面只有二進位制,所以此時往通道里面放入非二進位制格式內容的話就需要指定一種編碼方式。除非你直接在file.write()函式中直接寫二進位制,但是人類是不可能記住那麼多二進位制組合所代表的含義的。

然後我們來讀取一下上面建立的名字為“二進位制”的這個檔案

with open(`二進位制`,`rb`) as file:
    print(`不指定解碼方式時的結果:`,file.read())

如果我們在讀取時不指定解碼方式,那麼輸出的結果就是下面這種人類無法理解的奇怪的東西(實際上它是用16進製表示的二進位制)

不指定解碼方式時的結果: b`xe5x8dx81xe6xadxa5xe6x9dx80xe4xb8x80xe4xbaxbaxefxbcx8cxe5x8dx83xe9x87x8cxe4xb8x8dxe7x95x99xe8xa1x8c`

所以我們在讀取時也要指定編碼:

with open(`二進位制`,`rb`) as file:
    content = file.read()
    print(`指定解碼方式後的結果:`,content.decode(`utf-8`))

指定解碼方式後的結果: 十步殺一人,千里不留行

其他的在讀取時可能用到的方法:

readline()讀取一行,如果裡面新增了引數n,則會讀取n個字元(我覺得這是個bug,貌似沒什麼卵用)
readlines()讀取所有內容,結果返回一個列表,元素由每一行組成。
tell()會輸出當前指標的位置,注意,該位置是以位元組來計算的,不是字元
seek()重新指定指標位置。

使用readlines()並不是一個好方法,因為是一次性將檔案都讀取到記憶體中,如果檔案較大時則造成記憶體溢位,實際中使用下面的方法,系統會自動生成一個迭代器,用迭代的方法把需要的資料取出來。

with open(`libai`) as f:
    for i in f: #這裡的i實際就是迭代後的每一行,用for迴圈的方式從檔案物件中取出來,取一行讀一行,節省記憶體
        print(i)
趙客縵胡纓,吳鉤霜雪明。銀鞍照白馬,颯沓如流星。

十步殺一人,千里不留行。事了拂衣去,深藏身與名。

閒過信陵飲,脫劍膝前橫。將炙啖朱亥,持觴勸侯嬴。

三杯吐然諾,五嶽倒為輕。眼花耳熱後,意氣素霓生。

救趙揮金錘,邯鄲先震驚。千秋二壯士,烜赫大梁城。  

縱死俠骨香,不慚世上英。誰能書閣下,白首太玄經。

關閉檔案


如果用with open() 來開啟檔案的話就不用管關閉檔案的操作了,因為Python已經幫你完成了這一步,否則必須在處理檔案之後加上關閉檔案的操作:file_name.close()

相關文章