掌握了編寫組織有序而易於使用的程序所需的基本技能,該考慮讓程序目標更明確,用途更大。本章中,將學習處理文件,讓程序可以快速地分析大量的數據;咱們將學習錯誤處理,避免程序在面對意外情形時崩潰;咱們將學習異常,它們是Python建立的特殊對象,用於管理程序運行時出現的錯誤;咱們還將學習模塊json,它讓咱們可以保存用戶數據,以避免在程序中止運行後丟失。python
學習處理文件和保存數據可讓咱們的程序使用起來更容易:用戶將可以選擇輸入什麼樣的數據,以及在何時輸入;用戶使用咱們的程序作一些工做後,可將程序關閉,之後在接着往下作。學習處理異常可幫助咱們應對文件不存在的情形,以及處理其餘可能致使程序崩潰的問題。這讓咱們的程序在面對錯誤的數據時更健壯——無論這些錯誤數據源自無心的錯誤,仍是源自破壞程度的惡意企圖。咱們在本章學習的技能可提升程序的適用性、可用性和穩定性。git
10.1 從文件中讀取數據編程
文本文件可存儲的數據量巨大。每當須要分析或者修改存儲在文件中的信息時,讀取文件都頗有用,對數據分析應用程序來講尤爲如此。例如:你能夠編寫一個程序:讀取文本文件的內容,從新設置這些數據格式並將其寫入文件,讓瀏覽器能顯示這些內容。json
要使用文本文件中信息,首先須要將信息讀取到內存中。爲此,你能夠一次性讀取文件所有內容,也能夠每次一行的方式逐步讀取。瀏覽器
10.1.1 讀取整個文件數據結構
要讀取文件,須要一個包含幾行文本的文件。下面首先來建立一個文件,它包含精確到小數點後30位的圓周率值,且在小數點後每10位處都換行:app
pi_digits.text編程語言
3.1415926535 8979323846 2643383279
要手動嘗試後續示例,可在編輯器中輸入這些數據行,再將文件保存爲pi_digits.text.編輯器
file_reader.pyide
with open('pi_digits.text') as file_object: contents = file_object.read() print(contents)
這個程序中,第1行代碼作了大量工做。咱們先來看看函數open()。要以任何方式使用文件——哪怕僅僅是打印內容,都得打開文件,這樣才能訪問它。函數open()接收一個參數:要打開文件名稱。python在當前執行的文件所在的目錄中查找指定的文件。在這個示例中,當前運行的是file_reader.py,所以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會在合適時候自動關閉。
有了表示pi_digits.txt的文件對象後,咱們使用方法read()(程序前2行)讀取這個文件的所有內容,並將其做爲一個長的字符串存儲在變量contents中。這樣,經過打印contents的值,就可將這個文本文件的所有內容顯示出來。
相比於原始文件,改輸出惟一不一樣的地方是末尾多了一個空行。由於read()到達文件末尾時返回一個空字符串,而這個空字符串顯示出來時就是一個空行。要刪除多出來的空行,可在print()語句中使用rstrip():
with open('pi_digits.text') as file_object: contents = file_object.read() print(content.srstrip())
python方法rstrip()刪除字符串末尾空白。
10.1.2 文件路徑
當你將相似pi_digits.txt這樣簡單的文件名傳遞給函數open()時,python將在當前執行的文件(即.py程序文件)所在的目錄中查找文件。
根據你組織的文件方式,有時可能要打開不在程序文件所屬目錄中文件。例如,你可能將程序文件存儲在了文件夾 python_work 中,而在文件夾 python_work 中,有一個名爲text_files 的文件夾,用於存儲程序文件操做的文本文件。雖然文件夾 text_files 包含在文件夾 python_work 中,但僅向 open() 傳遞位於該文件夾中的文件的名稱也不可行,由於 Python只在文件夾 python_work 中查找,而不會在其子文件夾 text_files 中查找。要讓 Python 打開不與程序文件位於同一個目錄中的文件,須要提供 文件路徑 ,它讓 Python 到系統的特定位置去查找。
因爲文件夾text——files位於文件夾python_work中,所以可以使用相對文件路徑來打開改文件夾中的文件。相對文件路徑讓Python到指定的位置去查找,而該位置是相對於當前運行的程序所在目錄的。在Linux和OS X中,你能夠這樣編寫代碼:
with open('text_files/filename.txt') as file object:
這行代碼讓Python到文件夾python_work下的文件夾text_files中去查找指定的.txt文件。在Windows系統中,在文件路徑使用反斜槓(\ ):
with open('text_files\filename.txt') as file_object:
還能夠將文件再計算機中的準確位置告訴python,這樣就不要關心當前運行的程序存儲在什麼地方了。這稱爲絕對文件路徑。在相對路徑行不通時,可以使用絕對路徑。例:若是text_files並不在文件夾python_work中,而在文件夾other_files中,則向open()傳遞路徑‘text_files/ filename.txt’行不通,由於python只在文件夾Python_work中查找該位置。爲明確指出你但願python到哪裏去查找,你須要提供完整的路徑。
絕對路徑一般比相對路徑更長,所以將其存儲在一個變量中,再將該變量傳遞給open()會有所幫助。在Linux和OS X中,絕對路徑相似以下:
file_path = '/home/ehmatthes/other_files/text_files/filename.txt' with open(file_path) as file_object:
在Windows系統中,它們相似於這樣:
file_path = 'C:/Users/ehmatthes/other_files/text_files/filename.txt' with open(file_path) as file_object:
經過使用絕對路徑,可讀取系統任何地方的文件。就目前而言,最簡單的作法是,要買將數據文件存儲在程序文件中所在的目錄,要麼將存儲在程序文件所在目錄下的一個文件夾(如text_files)中。
注意:Windows系統有時可以正確地解讀文件路徑中的斜槓。若是你使用的是Windows系統,且結果不符合預期,確保在文件路徑中使用的是反斜槓。
10.1.3 逐行讀取
讀取文件時,經常要檢查其中的每一行:你可能要在文件中查找特定的信息,或者要以某種方式修改文件中的文本。例如,你可能要遍歷一個包含天氣數據的文件,並使用天氣描述中包含字樣sunny的行。在新聞報道中,你可額能會檢查包含標籤<headline>的行,並按特定格式設置它。
要以每次一行的方式檢查文件,可對文件對象使用for循環:
file_reader.py
filename = 'pi_digits.text' with open(filename) as file_object: for line in file_object: print(line)
在filename =‘pi_digits.txt’處,咱們將要讀取的文件名稱存儲在變量filename中,這是使用文件時一種常見作法。因爲變量filename表示的並不是實際文件——它只是一個讓python知道到哪裏去查找文件的字符串,所以可輕鬆地將‘pi_digits.txt’替換爲須要使用的另外一個文件名稱。調用open()後,將一個表示文件及其內容的對象存儲到變量file_object中(with open(filename)as file_object:)這裏也使用關鍵字with,讓python負責妥善地打開和關閉文件。爲查看文件內容,咱們經過對文件對象執行循環來遍歷文件中的每一行
咱們打印每一行時,發現空白行更多了,由於每行的末尾都有一個看不見的換行符,而print語句也會加上一個換行符,所以每行末尾都有兩個換行符:一個來自文件,另外一個來自print語句。要消除這些多餘的空白行,可在print語句中使用rstrip():
filename = 'pi_digits.txt' with open(filename) as file_object: for line in file_object: print(line.rstrip())
10.1.4 建立一個包含文件各行內容的列表
使用關鍵字with時,open()返回的文件對象只在with代碼塊內可用。若是要在with代碼塊外訪問文件的內容,可在with代碼塊內將文件的各行存儲在一個列表中,並在with代碼塊外使用該列表:你能夠當即處理文件的各個部分,也可推遲到程序後面再處理。
下面的實例在with代碼塊中將文件pi_digits.txt的各行存儲在一個列表中,再在with代碼塊外打印他們:
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.rstrip())
在lines = file_object.readlines()處的方法readlines()從文件中讀取每一行,並將其存儲在一個列表中;接下來,該列表被存儲到變量lines中;在with代碼塊中,咱們依然可使用這個變量。接下來,使用一個簡單的for循環打印lines中的各行。
10.1.5 使用文件內容
將文件讀取到內存中後,就能夠以任何方式使用這些數據。例:
首先建立一個字符串,它包含文件中存儲的全部數字,且沒有任何空格:
pi_string.py
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string +=line.strip() print(pi_string) print(len(pi_string))
輸出:
3.1415926535 8979323846 2643383279
36
先打開文件,將其中全部行都存儲在一個列表中。在pi_string = ' '處,咱們建立一個變量pi_string,用於存儲圓周率的值。接下來,使用一個循環將各行都加入到pi_string,並刪除每行末尾的換行符(rstrip())。
在變量pi_string存儲的字符串中,包含原來位於每行左邊的空格,爲刪除這些空格,可以使用strip()而不是rstrip():輸出結果:
輸出:
3.141592653589793238462643383279
32
注意 讀取文本文件時,python將其中的全部文本都解讀爲字符串。若是你讀取的是數字,並要建起做爲數值使用,就必須使用函數int()將其轉換爲整數,或使用函數float()將其轉換爲浮點數。
10.1.6 包含一百萬位的大型文件
若是有一個文本文件,須要到1000 000位圓周率的值,也可建立一個包含全部這些數字的字符串。爲此,無需對前面程序作任何修改,只需將這個文件傳遞給它。對於可處理的數據量,python沒有任何限制。
10.1.7 圓周率值中包含你的生日嗎
擴展所編寫程序,可將生日表示爲一個由數字組成的字符串,在檢查這個字符串是否包含在pi_string中:
filename = 'pi_million_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string += line.strip() birthday = input("Enter your birthday, in the form mmddyy:") if birthday in pi_string: print("your birthday appear in the first digits of pi!") else: print("Your birthday does not appear in the first million digits of pi")
練習:
10-1 Python學習筆記:在文本編輯器中新建一個文件,寫幾句話來總結一下你至此學到的Python只是,其中每一行都以「In Python you can」打頭。將這個文件命名爲learning_python.txt,並將其存儲到爲完成本章練習而編寫的程序所在的目錄中。編寫一個程序,它讀取這個文件,並將咱們所寫的內容打印三次:第一次打印是讀取整個文件;第二次打印時遍歷文件對象;第三次及打印是將各行存儲在一個列表中,再在with代碼塊外打印它們。
filename = 'lenrning_python'
with open(filename) as file_object:
contents = file_object.read()
print(contents)
with open(filename) as file_object:
for line in file_object:
print(line.strip())
print("\n")
with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.strip())
運行結果以下:
In Python you can lenrn how to develop game.
In python you can learn how to do peration and maintenance.
In python you can lenrn how to do data analysis.
In python you can learn a lot of things which you can think.
In Python you can lenrn how to develop game.
In python you can learn how to do peration and maintenance.
In python you can lenrn how to do data analysis.
In python you can learn a lot of things which you can think.
In Python you can lenrn how to develop game.
In python you can learn how to do peration and maintenance.
In python you can lenrn how to do data analysis.
In python you can learn a lot of things which you can think.
10-2 C語言學習筆記:可以使用方法replace()將字符串中的特定單詞都替換爲另外一個單詞。下面是一個簡單的示例,演示瞭如何將句子中的'dog'替換爲'cat':
>>> message = "I really like dogs"
>>> message.replace('dog','cat')
'I really like cats'
讀取咱們剛建立的文件lenrning_python.txt中的每一行,將其中的Python都替換爲另外一門語言的名稱,如C。將修改後的各行都打印到屏幕上。
filename = 'lenrning_python'
lines = []
with open(filename) as file_object:
for line in file_object:
string = line.replace('python',"C")
lines.append(string)
for message in lines:
print(message.strip())
運行若是以下:
In C you can lenrn how to develop game.
In C you can learn how to do peration and maintenance.
In C you can lenrn how to do data analysis.
In C you can learn a lot of things which you can think.
10.2.1寫入空文件
要將文本寫入文件,在調用open()時須要提供另外一個實參,告訴python要寫入打開的文件。
write_message.py
filename = 'programming.txt' with open(filename, 'w') as file_object: file_object.write("I love programming.")
這個示例中,調用open()時提供兩個實參(filename,‘w’)。第一個實參也是要打開的文件名稱;第二個實參(‘w’)告訴python,要以寫入模式打開這個文件。打開文件時,可指定讀取模式‘’('r')、寫入模式(‘w’)、附加模式(‘a’)或者讓你可以讀取和寫入文件的模式(‘r+’)。若是省略模式實參,python將以默認的制度模式打開文件。
注意:若是要寫入的文件不存在,函數open()將自動建立它。然而,以寫入(‘w’)模式打開文件時要當心,由於若是指定的文件已經存在,python將在返回文件對象前清空該文件。
在file_object.write(" ")處,咱們使用文件對象的方法write()將一個字符串寫入文件。這個程序沒有終端輸出,但若是你打開文件programming.txt,將看到以下一行內容:
I love programming.
相比於計算機中的其餘文件,這個文件沒有什麼不一樣。可編寫,操做等
注意:python只能講字符串寫入文本文件。要將數值數據存儲到文本文件中,必須先使用函數str()將其轉化爲字符串格式。
10.2.2 寫入多行
函數wirte()不會再你寫入的文本末尾添加換行符,所以若是你寫入多行時沒有指定換行符,文件看起來可能不是你但願的那:
filename = 'programming.txt' with open(filename, 'w') as file_object: file_object.write("I love programming.") file_object.write("I love creating new games.")
若是你打開programming.txt,將發現兩行內容擠在一塊兒:
I love programming.I love creating new games.
要讓每一個字符串度單獨佔一行,須要在write()語句中國包含換行符:
filename = 'programming.txt' with open(filename, 'w') as file_object: file_object.write("I love programming.\n") file_object.write("I love creating new games.\n")
10.2.3 附加到文件
若是要給文件添加內容,而不是覆蓋原有文件,能夠附加模式 打開文件。你以附加模式打開文件時,python不會在返回文件對象前清空文件,而你寫入到文件的行都將添加到文件末尾。若是指定的文件不存在,python會爲你建立一個空文件。
filename = 'programming.txt' with open(filename, 'a') as file_object: file_object.write("I also love finding meaning in large datasets.\n") file_object.write("I love creating apps that can run in a browser.\n")
在with open() as file_object:處,咱們打開文件時指定實參‘a’,以便將內容附加到文件末尾,而不是覆蓋文件原來內容。在後面繼續寫入2行,他們被添加到文件programming.txt末尾:
programming.txt
I love programming. I love creating new games. I also love finding meaning in large datasets. I love creating apps that can run in a browser.
最終的結果是,文件原來內容還在,後面是咱們新添加的內容。
練習:編寫一個 while 循環,提示用戶輸入其名字。用戶輸入其名字後,在屏幕上打印一句問候語,並將一條訪問記錄添加到文件 guest_book.txt 中。確保這個文件中的每條記錄都獨佔一行:
filename = 'guest.txt' with open(filename, 'a') as file_object: #此要求爲添加每一條記錄,'w'寫入會覆蓋 while True: print("Enter 'q' to quit the programmer:") guest_name = input("Please enter your name:") if guest_name=='q': break else: print("Hello "+ guest_name.title()+ ",welcome join us!") file_object.write(guest_name.title()+"visit this item.\n")
練習:
10-3 訪客:編寫一個程序,提示用戶輸入其名字;用戶作出響應後,將其名字寫入到文件guest.txt中。
分析:因爲是寫入,由於咱們不可能覆蓋前面一個來訪的人,這也是不被容許的,由於採用的追加模式。
filename = 'guest.txt'
with open(filename,'a') as file_object:
guest = input("Please input your name: ")
file_object.write(guest.title() + "\n")
10-4 訪客名單:編寫一個while循環,提示用戶輸入其名字,用戶輸入名字後,在屏幕上打印一條問候語,並將一條訪問記錄添加到文件
guest_book.txt中。確保這個文件中的天天記錄都獨佔一行。
filename = 'guest_book.txt'
with open(filename,'a') as file_object:
while True:
print("Enter 'q' to quit the programmer: ")
guest = input("Please input your name: ")
if guest == 'q':
break
else:
print("Hello, " + guest)
file_object.write(guest.title() + "\n")
運行結果以下:
Enter 'q' to quit the programmer:
Please input your name: geng changxue
Hello, geng changxue
Enter 'q' to quit the programmer:
Please input your name: q
10.3 異常
每當python發生不知所措的錯誤時,它會建立一個異常對象。若是你編寫處理該異常的代碼,程序將繼續運行;若是未處理,程序將中止,並顯示一個traceback,其中包含有關異常報告。
異常是使用try-except代碼塊處理的。try-except代碼塊讓python執行指定的操,同時告訴python發生異常時怎麼辦。使用try-except代碼塊時,即使出現異常,程序也將繼續運行:顯示你編寫好的友好的錯誤消息,而不是令用戶迷惑的traceback。
10.3.1 處理zeroDivisionError異常
division.py(一個簡單的異常)
pint(5/0)
一個traceback顯示:
Traceback (most recent call last): File "division.py", line 1, in <module> print(5/0) ZeroDivisionError: division by zero
最後一行ZeroDivisionError是一個異常對象。Python沒法按照你要求作的時候就會建立這種對象。下面咱們將告訴python發生這種錯誤怎麼辦
10.3.2 使用try-except代碼塊
當你你認爲可能發生錯誤時,可編寫一個try-except代碼塊來處理可能發生的異常。讓python嘗試運行一些代碼,並告訴它若是這些代碼發生了指定的異常,應怎麼辦。
處理ZeroDivisionError異常的try-except代碼塊相似於下面這樣:
try: print(5/0) except ZeroDivisionError: print("You can't divid by zero!")
咱們將致使錯誤的代碼行print(5/0)放在一個try代碼塊中。若是try代碼塊中的代碼運行起來沒有什麼問題,python將跳過except代碼塊;若是try代碼塊中的代碼致使錯誤,python將查找這樣的except代碼塊,並 運行期中代碼,即其中只等的錯誤與引起的錯誤相同。
這個示例中,try代碼塊中的代碼引起ZeroDivisionError異常,所以python指出該如何解決問題的except代碼塊,並運行其中代碼。這樣,用戶號看到的是一條友好錯誤消息,而不是traceback:
You can't divide by zero!
若是try-except代碼塊後面還有其餘代碼,程序將繼續運行,由於已經告訴python這種錯誤如何處理。
10.3.3 使用異常避免崩潰
若是程序可以妥善處理無效輸入,就能提示用戶有效輸入,而不至於崩潰。例(建立一個知心除法運算的簡單計算器):
division.py
print("Give me two numbers,and i'll divide them.") print("Enter 'q' to quit.") while True: first_number = input("\nFirst number: ") if first_number == 'q': break second_number = input("Second number: ") if second_number == 'q': break answer = int(first_number)/int(second_number) print(answer)
這個程序提示用戶輸入一個數字,並將其存儲到first_number中;若是用戶輸入的不是表示退出的q,就提示用戶再輸入一個數字,並將其存儲到變量second_number中。接下,計算這個兩個數字的商(answer)。這個程序沒有采起任何處理錯誤的措施,所以讓它執行除數爲0的除法運算時,它將奔潰:
Give me two numbers,and i'll divide them. Enter 'q' to quit. First number: 5 Second number: 0 Traceback (most recent call last): File "division.py", line 11, in <module> answer = int(first_number)/int(second_number) ZeroDivisionError: division by zero
程序奔潰,用戶將看到traceback。有時候,訓練有素的攻擊者將根據這些信息判斷對你的代碼發起什麼樣攻擊。
10.3.4 else 代碼塊
經過將可能引起錯誤的代碼放在try-except代碼塊中,可提供和這個程序抵禦錯誤的能力。錯誤是執行除法運算的代碼致使的,所以咱們須要將它放到try-except代碼塊中。經過示例還包含一個else代碼塊;依賴於try代碼塊成功執行的代碼都都應放到else代碼塊中:
print("Give me two numbers,and i'll divide them.") print("Enter 'q' to quit.") while True: first_number = input("\nFirst number: ") if first_number == 'q': break second_number = input("Second number: ") if second_number == 'q': break try: answer = int(first_number)/int(second_number) except ZeroDivisionError: print("You can't divide by 0!") else: print(answer)
咱們讓python嘗試執行try代碼塊中的除法運算,這個代碼塊只包含可能致使錯誤的代碼。依賴於try代碼塊的執行都放在else代碼塊中;在這個示例中,若是除法成功,咱們使用else代碼塊來打印結果(如else代碼塊)。
except代碼塊告訴python,出現ZeroDivisionError異常時怎麼辦。若是try代碼塊因除零錯誤而失敗,咱們就打印一條友好的消息,告訴用戶如何避免這種錯誤。程序將繼續運行,用戶看到不到traceback:
Give me two numbers,and i'll divide them. Enter 'q' to quit. First number: 4 Second number: 0 You can't divide by 0! First number: 8 Second number: 9 0.8888888888888888
try-except-else代碼塊工做原理大體以下:python嘗試執行try代碼塊中的代碼;只有可能引起異常的代碼才須要放在try語句中。有時候,有一些僅在try代碼塊成功執行時才須要的運行的代碼;這些代碼應放在else代碼塊中。except代碼塊告訴python,若是它嘗試運行try代碼塊中的代碼引起指定的異常該如何處理。
經過預測可能發生的錯誤代碼,可編寫健壯的程序,他們即使面臨無效數據或缺乏資源,也能繼續運行,從而抵禦無心的用戶錯誤和惡意攻擊。
10.3.5 處理FileNotFoundError異常
使用文件時,一種常見的問題是找不到文件:你要查找的文件可能在其餘地方、文件名可能不正確或者這個文件根本就不存在。對於全部這些情形,均可使用 try-except 代碼
塊以直觀的方式進行處理。
咱們來嘗試讀取一個不存在的文件。下面的程序嘗試讀取文件 alice.txt 的內容,但我沒有將這個文件存儲在 alice.py 所在的目錄中。
alice.py
filename = 'alice.txt' with open(filename) as f_obj: contents = f_ojc.read()
python沒法讀取不存的文件,所以它引起一個異常:
Traceback (most recent call last): File "alice.py", line 3, in <module> with open(filename) as f_obj: FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
在上述的traceback中,最後一行報告了FileNotFoundError異常,這是Python找不到要打開的文件時建立的異常。這個示例中,這個錯誤就是函數open()致使的,所以要處理這個錯誤,必須將try語句放在open()代碼以前:
filename = 'alice.txt' try: with open(filename) as f_obj: contents = f_ojc.read() except FileNotFoundError: msg = "sorry, the file "+ filename +"does not exist." print(msg)
在這個示例中,try代碼塊引起FileNotFoundError異常,所以python找出與該錯誤匹配的except代碼塊,並運行其中的代碼。最終的結果四顯示一條友好的錯誤消息,而不是traceback:
Sorry,the file alice.txt does not exist.
10.3.6 分析文本
下面來提取通話Alice in wonderland的文本,並嘗試計算它包含多少個單詞。咱們將使用方法split(),它根據一個字符串建立一個單詞列表。下面是對只包含童話名「Alice in Wonderland」的字符串調用方法split()的結果:
title = "Alice in Wonderland"
title.split()
['Alice', 'in', 'Wonderland']
方法split()以空格爲分隔符將字符串拆分紅多個部分,
並將這些部分都存儲到一個列表中。結果是一個包含字符串中全部單詞的列表,雖然有些單詞可能包含標點。爲計算Alice in Wonderland 包含多少個單詞,咱們將對整篇小說調用 split() ,再計算獲得的列表包含多少個元素,從而肯定整篇童話大體包含多少個單詞:
filename = 'alice.txt' try: with open(filename) as f_obj: contents = f_obj.read() except FileNotfoundError: msg = "sorry, the file "+ filename + " does not exist." print(msg) else: #計算文件大體包含多少個單詞 words = contents.split() num_words = len(words) print("The file "+ filename+ "has about "+str(num_words)+ "words.") 輸出: The file alice.txthas about 29397words. 或:sorry, the file alice.txt does not exist.
把alice.txt移到正確的目錄中,讓try代碼塊可以成功滴執行。在words = contents處,對變量contents(如今它是長字符串,包含通話Alice in Wordland的所有文本)調用方法split(),以生成一個列表,其中包含這部童話中的全部單詞。當咱們使用len()來肯定這個列表的長度時,就知道原始字符串打字包含多少個單詞。這些代碼都放在else代碼塊中,僅當try代碼塊成功執行時候帶執行他們。
10.3.7 使用多個文件
先將程序大部分代碼移到一個名爲count_words()的函數中,這樣對多本書分析時候將更容易:
Word_count.py
def count_words(fliename): try: with open(filename) as f_obj: contents = f_obj.read() except FileNotFoundError: msg = "Sorry, the file "+ filename +" does not exist." print(msg) else: words = contents.split() num_words = len(words) print("The file "+ filename +" has about "+ str(num_words)+ " words.") filename = 'alice.txt' count_words(filename)
這些代碼大都與原來同樣,咱們只是將它們移到了函數 count_words() 中,並增長了縮進量。修改程序的同時更新註釋是個不錯的習慣,所以咱們將註釋改爲了文檔字符串。
如今能夠編寫一個簡單的循環,計算要分析的任何文本包含多少個單詞了。爲此,咱們將要分析的文件的名稱存儲在一個列表中,而後對列表中的每一個文件都調用coun_words()。
def count_words(fliename): --snip-- filenames = ['alice.txt','siddhatha.txt', 'moby.txt', 'little_woman.txt'] for filename in filenames: count_words(filename)
輸出:
The file alice.txt has about 29397 words.
Sorry, the file siddhatha.txt does not exist.
Sorry, the file moby.txt does not exist.
Sorry, the file little_woman.txt does not exist.
在這個示例中,使用 try-except 代碼塊提供了兩個重要的優勢:避免讓用戶看到 traceback ;讓程序可以繼續分析可以找到的其餘文件。若是不捕獲因找不到 siddhartha.txt 而引起的FileNotFoundError 異常,用戶將看到完整的 traceback ,而程序將在嘗試分析 Siddhartha 後中止運行 —— 根本不分析 Moby Dick 和 Little Women 。
10.3.8 失敗時一聲不吭
要讓程序在失敗時一聲不吭,可像一般那樣編寫 try 代碼塊,但在 except 代碼塊中明確地告訴 Python 什麼都不要作。 Python 有一個 pass 語句,可在代碼塊中使用它來讓 Python什麼都不要作:
def count_words(fliename): --snip-- except FileNotFoundError: pass #在Python代碼塊中跳過 else: --snip-- filenames = ['alice.txt','siddhatha.txt', 'moby.txt', 'little_woman.txt'] for filename in filenames: count_words(filename)
輸出:
The file alice.txt has about 29397 words.
出現 FileNotFoundError 異常時,將執行 except 代碼塊中的代碼,但什麼都不會發生。這種錯誤發生時,不會出現 traceback ,也沒有任何輸出。用戶將看到存在的每一個文件包含多少個單詞,但沒有任何跡象代表有一個文件未找到。
pass 語句還充當了佔位符,它提醒你在程序的某個地方什麼都沒有作,而且之後也許要在這裏作些什麼。
10.4 存儲數據
不少程序都要求用戶輸入某種信息,如讓用戶存儲遊戲首選項或提供要可視化的數據。無論專一的是什麼,程序都把用戶提供的信息存儲在列表和字典等數據結構中。用戶關閉程序時,你幾乎老是要保存他們提供的信息;一種簡單的方式是使用模塊 json 來存儲數據。
模塊 json 讓你可以將簡單的 Python 數據結構轉儲到文件中,並在程序再次運行時加載該文件中的數據。你還可使用 json 在 Python 程序之間分享數據。更重要的是, JSON 數據格式並不是 Python 專用的,這讓你可以將以 JSON 格式存儲的數據與使用其餘編程語言的人分享。
注意:JSON(JavaScript Object Notation)格式最初是JavaScript開發的,但隨後成了一種常見格式,被包括python在內的衆多語言採用。
10.4.1 使用json.dump()和json.load()
接下來編寫一個存儲一組數字的簡短程序,再編寫一個將這些數字讀取到內存中的程序。第一個程序將使用json.dump()來存儲這組數字,而第二個程序將使用json.load()。
函數json.dump()接收兩個實參:要存儲數據以及可用於存儲數據的文對象。下面演示如何使用json.dump()來存儲數字列表。
number_writer.py
import json numbers = [2, 3, 4, 5, 6, 7, 11, 13] filename = 'numbers.json' with open(filename, 'w') as f_obj: json.dump(numbers,f_obj)
咱們先導入模塊 json ,再建立一個數字列表。在filename處,咱們指定了要將該數字列表存儲到其中的文件的名稱。一般使用文件擴展名 .json 來指出文件存儲的數據爲 JSON 格式。接下來,咱們以寫入模式打開這個文件,讓 json 可以將數據寫入其中。後面,咱們使用函數 json.dump() 將數字列表存儲到文件 numbers.json 中。
這個程序沒有輸出,但咱們能夠打開文件 numbers.json ,看看其內容:
[2, 3, 4, 5, 6, 7, 11, 13]
下面再編寫一個程序,使用 json.load() 將這個列表讀取到內存中:
number_reader.py
import json try: filename = 'numbers.json' with open(filename) as f_obj: numbers = json.load(f_obj) except FileNotFoundError: msg = "sorry, the file"+ filename+" is not exist." print(msg) else: print(numbers) 輸出: [2, 3, 4, 5, 6, 7, 11, 13]
此次咱們以讀取方式打開這個文件,由於 Python 只需讀取這個文件。使用函數 json.load() 加載存儲在numbers.json 中的信息,並將其存儲到變量 numbers 中。
10.4.2 保存和讀取用戶生成的數據
對於用戶生成的數據,使用 json 保存它們大有裨益,由於若是不以某種方式進行存儲,等程序中止運行時用戶的信息將丟失。下面來看一個這樣的例子:用戶首次運行程序時被提示輸入本身的名字,這樣再次運行程序時就記住他了。先來存儲用戶的名字:
remember_me.py
import json username = input("what's your name?\n") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back, "+username +"!") 輸出: what's your name? Mack we will remember you when you come back, Mack!
咱們提示輸入用戶名,並將其存儲在一個變量中。接下來,咱們調用 json.dump() ,並將用戶名和一個文件對象傳遞給它,從而將用戶名存儲到文件中。
如今再編寫一個程序,向其名字被存儲的用戶發出問候:
greet_user.py
import json filename = 'username.json' with open(filename) as f_obj: #讀與寫入的差別,with open(filename,'w') as f_obj: username = json.load(f_obj) #存儲和讀處差別 json.dump(username, f_obj) print("wlcome back, "+ username +"!") 輸出: wlcome back, Mack!
咱們使用 json.load() 將存儲在 username.json 中的信息讀取到變量 username 中。恢復用戶名後,咱們就能夠歡迎用戶回來了。
咱們須要將這兩個程序合併到一個程序( remember_me.py )中。這個程序運行時,咱們將嘗試從文件 username.json 中獲取用戶名,所以咱們首先編寫一個嘗試恢復用戶名的 try 代碼塊。若是這個文件不存在,咱們就在 except 代碼塊中提示用戶輸入用戶名,並將其存儲在 username.json 中,以便程序再次運行時可以獲取它:
remember_me.py
import json #若是之前存儲了用戶名,就加載 #不然,就提示用戶輸入用戶名並存儲它 filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input("what is your name?\n") with open(filename, 'w') as f_obj: json.dump(username, f_obj) print("we'll remember you when you come back, "+ username +"!") else: print("welcome back "+ username +"!")
在第一個open(filename) as f_obj處,嘗試打開文件 username.json 。若是這個文件存在,就將其中的用戶名讀取到內存中username = json.load(f_obj),再執行 else 代碼塊,即打印一條歡迎用戶回來的消息。用戶首次運行這個程序時,文件 username.json 不存在,將引起 FileNotFoundError 異常,所以 Python 將執行 except 代碼塊:提示用戶輸入其用戶名,再使用 json.dump() 存儲該用戶名,並打印一句問候語。
不管執行的是 except 代碼塊仍是 else 代碼塊,都將顯示用戶名和合適的問候語。若是這個程序是首次運行,輸出將以下:
what is your name? Mack we'll remember you when you come back, Mack!
不然,輸出:
welcome back Mack!
10.4.3 重構
代碼可以正確地運行,但可作進一步的改進 —— 將代碼劃分爲一系列完成具體工做的函數。這樣的過程被稱爲 重構 。重構讓代碼更清晰、更易於理解、更容易擴展。
要重構 remember_me.py ,可將其大部分邏輯放到一個或多個函數中。 remember_me.py 的重點是問候用戶,所以咱們將其全部代碼都放到一個名爲 greet_user() 的函數中:
remember_me.py
import json def greet_user(): """問候用戶,並指出其名字""" filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input("what's your name?\n") with open(filename, 'w') as f_obj: json.dump(username, f_obj) print("we'll remember you when you come back, "+ username +"!") else: print("welcome back "+ username +"!") greet_user()
函數 greet_user() 所作的不只僅是問候用戶,還在存儲了用戶名時獲取它,而在沒有存儲用戶名時提示用戶輸入一個。
下面來重構 greet_user() ,讓它不執行這麼多任務。爲此,咱們首先將獲取存儲的用戶名的代碼移到另外一個函數中:
import json def get_sorted_username(): """若是存儲了用戶名,就獲取它""" filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return username def greet_user(): """問候用戶,並指出其名字""" username = get_sorted_username() if username: print("welcome back,"+username +"!") else: username = input("what's your name?\n") filename = 'username.json' with open(filename, 'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back,"+ username +"!") greet_user()
新增的函數 get_stored_username() 目標明確,開始處的文檔字符串指出了這一點。若是存儲了用戶名,這個函數就獲取並返回它;若是文件 username.json 不存在,這個函數就返回 None 。這是一種不錯的作法:函數要麼返回預期的值,要麼返回 None ;這讓咱們可以使用函數的返回值作簡單測試。在if username:處,若是成功地獲取了用戶名,就打印一條歡迎用戶回來的消息,不然就提示用戶輸入用戶名。
需將 greet_user() 中的另外一個代碼塊提取出來:將沒有存儲用戶名時提示用戶輸入的代碼放在一個獨立的函數中:
import json def get_sorted_username(): """若是存儲用戶名,就獲取它""" --snip-- def get_new_username(): """提示用戶輸入用戶名""" username = input("What's your name?\n") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username, f_obj) return username def greet_user(): """問候用戶,並指出其名字""" username = get_sorted_username() if username: print("welcome back,"+username +"!") else: username = input("what's your name?\n") filename = 'username.json' with open(filename, 'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back,"+ username +"!") greet_user()
在 remember_me.py 的這個最終版本中,每一個函數都執行單一而清晰的任務。調用 greet_user() ,它打印一條合適的消息:要麼歡迎老用戶回來,要麼問候新用戶。爲此,它首先調用 get_stored_username() ,這個函數只負責獲取存儲的用戶名(若是存儲了的話),再在必要時調用 get_new_username() ,這個函數只負責獲取並存儲新用戶的用戶名。要編寫出清晰而易於維護和擴展的代碼,這種劃分工做必不可少
10.5 小結
本章中,你學習了:如何使用文件;如何一次性讀取整個文件,以及如何以每次一行的方式讀取文件的內容;如何寫入文件,以及如何將文本附加到文件末尾;什麼是異常以及如何處理程序可能引起的異常;如何存儲 Python 數據結構,以保存用戶提供的信息,避免用戶每次運行程序時都須要從新提供。
練習:
喜歡的數字:編寫一個程序,提示用戶輸入他喜歡的數字,並使用json.dump()將這個數字存儲到文件中。在編寫一個程序,從文件中讀取這個值並打印消息"I know your favorite number!It's ."
import json filename = 'favorite.json' with open(filename, 'w') as f_obj: """先打開文件,再寫入用戶輸入""" favorite_num = input("What's your favorite num: ") json.dump(favorite_num, f_obj) #dump()第一個元素指代 input內容爲favorite_num print("we'll remember your favorite number "+str(favorite_num)+".") import json filename = 'favorite.json' with open(filename) as f_obj: json.load(filename,'r') #更正 M = json.load(f_obj) print("I know your favorite number! It's "+ str(filename)) #更正 str(M)
記住喜歡的數字:將上個練習中的兩個程序合二爲一。若是存儲了用戶喜歡的數字,就向用戶顯示它,不然提示用戶輸入他喜歡的數字並將其存儲到文件中。運行這個程序兩次,看是否向預期那樣工做。
import json """導入模塊json""" def get_stored_favorite_num(): """查看是否存在用戶喜歡的數字""" try: filename = 'lucky_number.json' with open(filename) as f_obj: favorite_number = json.load(f_obj) except FileNotFoundError: return None else: return favorite_number def get_favorite_num(): #若是文件不存在,則讓用戶從新輸入並存儲它 favorite_number = input("please enter your favorite number: ") filename = 'lucky_number.json' with open(filename,'w') as f_obj: json.dump(favorite_number,f_obj) return favorite_number def favorite_num(): #返回用戶新歡的數字 favorite_number = get_stored_favorite_num() if favorite_number: print("I know your favorite number is "+ str(favorite_number)+".") else: favorite_number = get_favorite_num() print("I'll remember your favorite number: "+str(favorite_number)) favorite_num()
10-13 驗證用戶:最後一個remember_me.py版本假設用戶要麼已輸入其用戶名,要麼是首次運行該程序。咱們應修改這個程序,以應對這樣的情形:當前和最後一次運行該程序的用戶並不是同一我的。 爲此,在greet_user()中打印歡迎回來的消息前,先詢問他用戶名是不是對的。若是不對,就調用get_new_username()讓用戶輸入正確的用戶名。import jsondef get_stored_username(): '''若是存儲了用戶名,就獲取它''' try: filename = 'username.json' with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: while True: #判斷用戶名是否相同,這裏要求用戶輸入相同的名字咱們才歡迎他,否則會一直讓用戶輸入 yourname = input("Enter your username: ") if username == yourname: return username break else: print("\nThe user name you entered is incorrect, please re-enter.")def get_new_username(): '''若是沒有存儲用戶名,就生成一個新的文件存儲用戶名''' username = input("What is your name? ") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) return usernamedef greet_user(): '''問候用戶,並指出其名字''' username = get_stored_username() if username: print("Welcome back, " + username + "!") else: username = get_new_username() print("We'll remember you when you come back, " + username + "!")greet_user()