1. 程式人生 > 實用技巧 >檔案和檔案異常

檔案和檔案異常

文章總覽圖

一,從檔案中讀取資料

每當需要分析或修改儲存在檔案中的資訊時,讀取檔案都很有用,對資料分析應用程式來說也非常重要。

讀取一個文字檔案的內容,重新設定這些資料的格式並將其寫入檔案,讓瀏覽器能夠顯示這些內容。

要使用檔案檔案中的資訊,首先需要將資訊讀取到記憶體中。可以一次性讀取檔案的全部內容,也可以每次一行的方式逐步讀取。

1.讀取整個檔案

先建立一個包含幾行文字的檔案。將檔案儲存至該程式所在目錄中。

輸出:

函式open()接受一個引數:要開啟的檔案的名稱。Python在當前執行的檔案所在的目錄中查詢指定的檔案。在這個示例中,當前執行的是file_reader.py所在的目錄中查詢pi_digits.txt。函式open()返回一個表示檔案的物件。在這裡,open('pi_digits.txt')返回一個表示檔案pi_digits.txt的物件。Python將這個物件儲存在我們將在後面使用的變數中。

關鍵字with在不再需要訪問檔案後將其關閉。這個程式中,呼叫了open(),但沒有呼叫close()。也可以呼叫open()和close()來開啟和關閉檔案,這樣做,如果程式存在bug,導致close()語句未執行,檔案將不會關閉。未妥善關閉檔案可能會導致資料丟失或受損。如果在程式中過早地呼叫close(),會發現需要使用檔案時它已關閉,這會導致更多的錯誤。

並非在任何情況下都能輕鬆確定關閉檔案的恰當時機,但通過使用前面所示的結構,可讓Python去確定:只管開啟檔案,並在需要時使用它,Python自會在合適的時候將其自動關閉。

使用方法read()讀取這個檔案的全部內容,並將其作為一個長長的字串儲存在變數contents中。通過列印contents的值,就可將這個文字檔案的全部內容顯示出來。

為什麼多出個空行?因為read()到達檔案末尾時返回一個空字串,而將這個空字串顯示出來時就是一個空行。要刪除末尾的空行,可在print語句中使用rstrip()。

輸出:

2.檔案路徑

將類似pi_digits.txt這樣的簡單檔名傳遞給函式open()時,Python將在當前執行的檔案(即.py程式檔案)所在的目錄中查詢檔案。

根據組織檔案的方式,有時可能要開啟不在程式檔案所屬目錄中的檔案。要讓Python開啟不與程式檔案位於同一個目錄中的檔案,需要提供檔案路徑,它讓Python到系統的特定位置去查詢。

相對路徑:

由於資料夾text_files位於資料夾python_work中,因此可使相對檔案路徑來開啟該資料夾中的檔案。相對檔案路徑讓Python到指定的位置去查詢,而該位置是相對於當前執行的程式所在目錄的。

Python到資料夾python_work下的資料夾text_files中去查詢指定的.txt檔案。

Windows裡是\但在字串裡需要轉義,所以得寫成\\。(字串就是在例子中用單引號引起來的內容)。‘


輸出:

將檔案在計算機中的準確位置告訴Python,這樣就不用關心當前執行的程式儲存在什麼地方了。這稱為絕對檔案路徑。相對路徑行不通時,可使用絕對路徑。

絕對路徑:

絕對路徑通常比相對路徑更長,因此將其儲存在一個變數中,再將該變數傳遞給open()會有所幫助。


輸出:


通過使用絕對路徑,可讀取系統任何地方的檔案。Windows系統有時能夠正確地解讀檔案路徑中的斜槓。由於反斜槓在Python中被視為轉義標記,為在Windows中確保萬無一失,應以原始字串的方式指定路徑,即在開頭的單引號前加上r。


輸出:


3.逐行讀取

讀取檔案時,常常需要檢查其中的每一行:可能要在檔案中查詢特定的資訊,或者要以某種方式修改檔案中的文字。

要以每次一行的方式檢查檔案,可對檔案物件使用for迴圈。

將要讀取的檔案的名稱儲存在變數filename中。由於變數filename表示的並非實際檔案,它只是一個讓Python知道到哪裡去查詢檔案的字串。可將'pi_digits.txt'替換為要使用的另一個檔案的名稱。呼叫open()後,將一個表示檔案及其內容的物件儲存到了變數file_object中。這裡也使用了關鍵字with,讓Python負責妥善地開啟和關閉檔案。為檢視檔案的內容,我們通過對檔案物件執行迴圈來遍歷檔案中的每一行。

輸出:


列印每一行時發現空白行更多了。因為在這個檔案中,每行的末尾都有一個看不見的換行符,而print語句也會加上一個換行符,因此每行末尾都有兩個換行符:一個來自檔案,另一個來自print語句。要消除這些多餘的空白行,可在print語句中使用rstrip()。

輸出:


4.建立一個包含檔案各行內容的列表

使用關鍵字with時,open()返回的檔案物件只在with程式碼塊內可用。如果要在with程式碼塊外訪問檔案的內容,可在with程式碼塊內將檔案的各行儲存在一個列表中,並在with程式碼塊外使用該列表:可以立即處理檔案的各個部分,也可推遲到程式後面再處理。

輸出:

方法readlines()從檔案中讀取每一行,並將其儲存在一個列表中。接下來,該列表被儲存到變數lines中。在with程式碼塊外,我們依然可以使用這個變數。我們使用一個簡單的for迴圈來列印lines中的各行。由於列表lines的每個元素都對應於檔案中的一行,因此輸出與檔案內容一致。

5.使用檔案中的內容

將檔案讀取到記憶體後,可以以任何方式使用這些資料了。

首先開啟檔案,並將其中的所有行都儲存在一個列表中。建立一個變數pi_string,用於儲存圓周率的值。使用一個迴圈將各行都加入pi_string,並刪除每行末尾的換行符。列印這個字串以及長度。

輸出:

在變數pi_string儲存的字串中,包含原來位於每行左邊的空格,為刪除這些空格,可使用strip()而不是rstrip()。

輸出:


獲得一個這樣的字串:它包含精確到30位小數的圓周率值。這個字串長32字元,因為它還包含整數部分的3和小數點。

讀取文字檔案時,Python將其中的所有文字都解讀為字串。如果讀取的是數字,並要將其作為數值使用,就必須使用函式int()將其轉換為整數,或使用函式float()將其轉換為浮點數。

6.包含一百萬位的大型檔案

有一個文字檔案,其中包含精確到小數點後100萬位而不是30位的圓周率值,也可建立一個包含所有這些數字的字串。無需對程式做任何修改,只需將這個檔案傳遞給它即可。只打印小數點後50位,以免終端為顯示100萬位不斷翻滾。

輸出:


7.圓周率值中包含自己的生日

輸出:

二,寫入檔案

儲存資料的最簡單的方式之一是將其寫入到檔案中。通過將輸出寫入檔案,即便關閉包含程式輸出的終端視窗,這些輸出也依然存在:可以在程式結束執行後檢視這些輸出,可與別人分享輸出檔案,還可編寫程式來將這些輸出讀取到記憶體中並進行處理。

1.寫入空檔案

要將文字寫入檔案,在呼叫open()時需要提供另一個實參,告訴Python要寫入開啟的檔案。

空檔案:


程式碼:


呼叫open()時提供了兩個實參。第一個實參也是要開啟檔案的名稱。第二個實參('w')告訴Python,我們要以寫入模式開啟這個檔案。開啟檔案時,可指定讀取模式('r'),寫入模式('w'),附加模式('a')或讓你能夠讀取和寫入檔案的模式('r+')。如果省略了模式實參,Python將以預設的只讀模式開啟檔案。

如果要寫入的檔案不存在,函式open()將自動建立它。以寫入('w')模式開啟檔案時要小心,如果指定的檔案已經存在,Python在返回檔案物件前清空該檔案。

使用檔案物件的方法write()將一個字串寫入檔案。這個程式沒有終端輸出,如果開啟檔案programming.txt,看到其中包含如下內容:

Python只能將字串寫入文字檔案。要將數值資料儲存到文字檔案中,必須先使用函式str()將其轉換為字串格式。

輸出:


2.寫入多行

函式write()不會在寫入的文字末尾新增換行符,如果寫入多行時沒有指定換行符:

輸出:


讓每個字串都單獨佔一行,需要在write()語句中包含換行符:

輸出:

像顯示到終端的輸出一樣,還可以使用空格、製表符和空行來設定這些輸出的格式。

3.附件到檔案

要給檔案新增內容,而不是覆蓋原有內容,可以附加模式開啟檔案。以附加模式開啟檔案時,Python不會在返回檔案物件前清空檔案,而寫入到檔案的行都將新增到檔案末尾。如果指定的檔案不存在,Python將建立一個空檔案。

輸出:


開啟檔案時指定了實參'a',以便將內容附加到檔案末尾,而不是覆蓋檔案原來的內容。又寫入了兩行,它們被新增到檔案programming.txt末尾。

三,異常

Python使用被稱為異常的特殊物件來管理程式執行期間發生的錯誤。每當發生讓Python不知所措的錯誤時,它都會建立一個異常物件。如果編寫了處理該異常的程式碼,程式將繼續執行。如果未對異常進行處理,程式將停止,並顯示一個traceback,其中包含有關異常的報告。

異常是使用try-except程式碼塊處理的。try-except程式碼塊讓Python執行指定的操作,同時告訴Python發生異常時怎麼辦。使用了try-except程式碼塊時,即便出現異常,程式也將繼續執行:顯示你編寫的友好的錯誤訊息,而不是令使用者迷惑的traceback。

1.處理ZeroDivisionError異常

輸出:

traceback中,指出的錯誤ZeroDivisionError是一個異常物件。Python無法按你的要求做時,就會建立這種物件。在這種情況下,Python將停止執行程式,並指出引發了哪種異常,而我們可根據這些資訊對程式進行修改。

2.使用try-except程式碼塊

覺得可能發生了錯誤時,可編寫一個try-except程式碼塊來處理可能引發的異常。

將導致錯誤的程式碼行print(5/0)放在了一個try程式碼塊中。如果try程式碼塊中的程式碼執行起來沒有問題,Python將跳過except程式碼塊。如果try程式碼塊中的程式碼導致了錯誤,Python將查詢這樣的except程式碼塊,並執行其中的程式碼,即其中指定的錯誤與引發的錯誤相同。

try程式碼塊中的程式碼引發了ZeroDivisionError異常,因此Python指出了該如何解決問題的except程式碼塊,並執行其中的程式碼。這樣,使用者看到的是一條友好的錯誤訊息,而不是traceback。

輸出:

如果try-except程式碼塊後面還有其它程式碼,程式將接著執行,因為已經告訴了Python如何處理這種錯誤。

3.使用異常避免崩潰

發生錯誤時,如果程式還有工作沒有完成,妥善處理錯誤就很重要。這種情況經常會出現在要求使用者提供輸入的程式中,如果程式能夠妥善處理無效輸入,就能再提示使用者提供有效輸入,而不至於崩潰。

輸出:

這個程式提示使用者輸入一個數字,並將其儲存到變數first_number中。如果使用者輸入的不是表示退出的q,就再提示使用者輸入一個數字,並將其儲存到變數second_number中。接下來,計算兩個數字的商。這個程式未採取任何處理錯誤的措施,因此讓它執行除數為0的除法運算時,它將崩潰。

不可讓使用者看到崩潰,使用者體驗不好。如果使用者懷有惡意,會通過traceback獲悉程式檔名稱,將看到部分不能正確執行的程式碼。根據這些資訊對你的程式碼進行攻擊。

4.else程式碼塊

通過將可能的引發錯誤的程式碼放在try-except程式碼塊中,可提高這個程式抵禦錯誤的能力。

輸出:

except程式碼塊告訴Python,出現ZeroDivisonError異常時怎麼辦。如果try程式碼塊因除0錯誤而失敗,就列印1條友好訊息,告訴使用者如何避免這種錯誤,程式繼續執行。

try-except-else程式碼塊的工作原理:

Python嘗試執行程式碼塊中的程式碼,只要可能引起異常的程式碼才需要放在try語句中。有一些僅在try程式碼塊成功執行時才需要執行的程式碼。這些程式碼應放在else程式碼塊中。except程式碼塊告訴Python,如果它嘗試執行try程式碼塊中的程式碼時引發了指定的異常,該如何處理。

6.分析文字

方法split(),根據一個字串建立一個單詞列表。

方法split()以空格為分隔符將字串分拆成多個部分,並將這些部分都儲存到一個列表中。結果是一個包含字串中所有單詞的列表,雖然有些單詞可能包含標點。


輸出:


7.使用多個檔案

輸出:

8.失敗時一聲不吭

Python中有個pass語句,在程式碼塊中使用它來讓Python什麼都不做。出現FilNotFoundError異常時,將執行except程式碼塊中的程式碼,但什麼都不會發生。這種錯誤發生時,不會出現traceback,也沒有任何輸出。沒有跡象表明有檔案沒找到。

pass語句充當佔符位,使用者看不到這個檔案,但可以讀取這個檔案,進而處理所有找不到檔案的問題。

輸出:

四,儲存資料

程式都把使用者提供的資訊儲存在列表和字典等資料結構中。使用者關閉程式時,總是要儲存它們提供的資訊。使用json來儲存資料。

模組json將簡單的Python資料結構轉儲到檔案中,並在程式再次執行時載入該檔案中的資料。可以使用json在Python程式之間分享資料。json格式不是Python專用的,可將以json格式儲存的資料與使用其它程式語言的人分享。

1.使用json.dump()和json.load()

函式json.dump()接受兩個實參:要儲存的資料以及可用於儲存資料的檔案物件。

使用函式json.dump()將數字列表儲存到檔案numbers.json中。

輸出:


使用函式json.load()載入儲存在numbers.json中的資訊,並將其儲存到變數numbers中。


輸出:


這是種在程式間共享資料的方式。

2.儲存和讀取使用者生成的資料

呼叫json.dump(),並將使用者和一個檔案物件傳遞給它,從而將使用者名稱儲存到檔案中。


輸出:


使用json.load()中的資訊讀取到變數username中。恢復使用者名稱後,就可以歡迎使用者回來了。


輸出:

3.重構

程式碼能正確地執行,但可以做進一步的改進。將程式碼劃分為一系列完成具體工作的函式。這樣的過程被稱為重構。

輸出:


函式greet_user()所做的不僅僅是問候使用者,還在儲存了使用者名稱時獲取它,而在沒有儲存使用者名稱時提示使用者輸入一個。

呼叫get_stored_username(),這個函式只負責獲取儲存的使用者名稱(如果已儲存)。呼叫get_new_username(),這個函式只負責獲取並存儲新使用者的使用者名稱。

歡迎掃碼關注!