Python學習之路9-檔案和異常

VPointer發表於2019-03-04

《Python程式設計:從入門到實踐》筆記。

本章主要是學習Python的檔案操作,主要是從檔案中讀取資料以及將資料儲存到檔案中,還有錯誤處理,異常類,json模組等。

1. 從檔案中讀資料

1.1 讀取整個檔案

以下檔案pi_digits.txt包含了精確到小數點後30位的圓周率資料

# pi_digits.txt檔案
3.1415926535
  8979323846
  2643383279

# 程式碼:
with open("pi_digits.txt", "r") as file_object:
    contents = file_object.read()   # 一次性讀取整個檔案
    print(contents)

# 結果:和上述檔案內容一樣
複製程式碼

從上述程式碼可以看出,我們開啟檔案使用open()函式,該函式至少接收一個引數,即檔案路徑。讀取檔案時需要向open()函式指明是用什麼方式讀取檔案,是隻讀("r"),只寫("w"),末尾新增("a")還是讀寫均可("r+"),open()函式預設以“只讀”方式讀取檔案。這只是4中常用的檔案讀取方式,此外還有至少8種讀寫方式。open()函式返回一個檔案物件,file_object用於接收該物件。通過檔案物件的read()方法讀取檔案內容,且該方法返回整個檔案的內容。

上述程式碼中的檔案和原始碼在同一目錄中。注意檔案路徑的問題,絕對路徑(不提倡)和相對路徑(相對於原始檔的路徑)以及Windows和Linux下路徑的寫法。

注意程式碼中的with關鍵字。其實讀寫檔案不需要該關鍵字,開啟檔案使用open()函式,檔案讀取完後關閉檔案使用close()函式,讀取內容可以呼叫read()方法。而之所以使用with關鍵字,主要是因為①你最後忘記關閉檔案,就想忘了關燈一樣;②也可能是在關閉前程式出錯,導致close()語句未執行。這些讓檔案沒有關閉的情況都有可能導致資料丟失或損壞。with關鍵字則被用來應對這些情況,它保證在結束with塊時,檔案一定會被關閉。

1.2 逐行讀取

上述程式碼一次性讀取整個檔案,這在檔案較小或者記憶體充裕的時候沒有問題,但如果檔案特別大,記憶體容量又很羞澀,則只能逐行讀取:

# 程式碼:
file_name = "pi_digits.txt"

with open(file_name) as file_pi:
    for line in file_pi:  # 也可以通過while迴圈配合readline()方法逐行讀取檔案
        print(line)

# 結果:
3.1415926535

  8979323846

  2643383279
複製程式碼

這裡需要注意一個問題,就是對行以及檔案末尾空字元的讀取問題,read()readline()方法會讀取末尾的空字元(這裡是換行符)。我們可以通過之前講的rstrip()方法去掉末尾的空字元。

1.3 將檔案每一行放入列表中

readlines()方法將檔案中每一行存入列表並返回,以下程式碼進一步處理檔案中的內容:

# 程式碼:
file_name = "pi_digits.txt"

with open(file_name) as file_pi:
    lines = file_pi.readlines()

pi_string = ""
for line in lines:
    pi_string += line.strip()

print(pi_string)
print(len(pi_string))

# 結果:
3.141592653589793238462643383279
32
複製程式碼

注意,Python從檔案中讀取出的所有內容都是字串,如果你想要的是數字,請記得轉換。

2. 寫入檔案

以下是一個簡單的檔案寫入程式:

filename = "python.txt"
with open(filename, "w") as file_obj:
    file_obj.write("I love python!")
複製程式碼

執行改程式碼後你會看到在同一目錄下會生成一個名為“python.txt"的檔案。需要注意的是,以"w"方式開啟檔案,如果要寫入的檔案不存在,則會自動建立該檔案;如果該檔案存在,該檔案的內容會被清空,然後再寫入。如果不想檔案被清空,請使用"a"(檔案指標放在檔案末尾)或"r+"(檔案指標指向檔案開頭)方式開啟檔案。還有一點,write()函式不會在檔案末尾新增換行符,如果需要換行符,請自行新增。

3. 異常

Python中使用被稱為異常的特殊物件來管理程式執行期間發生的錯誤。每當程式碼執行時如果遇到了不能處理的錯誤,Python都會建立一個異常物件,如果程式中沒有處理該物件的相關程式碼,程式將會停止,並顯示一個traceback,其中包含異常的相關報告。如果不想程式因為某些異常而終止執行,則需要我們使用try-except程式碼塊自行處理異常。以下是一個處理除零錯誤ZeroDivisionError的例子:

# 程式碼:
# 捕捉異常
try:
    resule = 5 / 0
except ZeroDivisionError:
    print("You can't divide by zero!\n")

# 不捕捉異常
print(5 / 0)

# 結果:
You can't divide by zero!

Traceback (most recent call last):
  File "division.py", line 29, in <module>
    print(5 / 0)
ZeroDivisionError: division by zero
複製程式碼

如果你打算編寫一個計算器應用,那麼這段程式碼必不可少。第一個例子表明,即使發生了異常,只要異常被我們捕捉,那麼程式便不會終止。如果只想捕捉異常,但暫時又不想處理,可以將上述的print("You can't divide by zero!\n")替換為pass語句。如果想捕獲所有的異常,則except後面不指定異常型別。

3.1 else程式碼塊

try-except程式碼塊還可以和else語句組合形成try-except-else程式碼塊,該結構表示,如果捕獲了異常,這執行except中的程式,沒有發生異常則執行else中的程式。以下程式是一個迴圈統計檔案中單詞數的例子,檔案讀取的部分被放到了函式中,該函式檢測有沒有發生FileNotFoundError

# 程式碼:
def count_words(filename):
    """計算一個檔案大致包含多少個單詞"""
    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.")

filenames = ["alice.txt", "siddhartha.txt", "moby_dick.txt", "little_women.txt"]
for filename in filenames:
    count_words(filename)

# 結果:
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189097 words.
複製程式碼

else的補充: 其實else不光可以和iftry-except結合,還可以和for迴圈和while迴圈結合,比如:

for i in range(10):
    pass
else:
    pass

i = 0
while i < 10:
    i++
else:
    pass
複製程式碼

這裡的else表示當迴圈結束後執行一些語句,比如提示之類的。

3.2 決定報告哪些錯誤

編寫得很好且經過詳盡測試的程式碼不容易出現內部錯誤,如語法或邏輯錯誤,但只要程式依賴於外部因素,如使用者輸入、存在指定的檔案、有網路連線等,就有可能出現異常。憑經驗可判斷改在程式的什麼地方包含異常處理塊,以及出現錯誤時該向使用者提供多少相關的資訊。

4. 儲存資料

很多程式要求使用者輸入某種資訊,也有可能程式中某些變數的資料在程式結束後不能丟失(比如機器學習最後訓練出來的模型引數),這是就需要將這些資訊以檔案的形式存下來。儲存資料的方式有很多,現在比較簡單且通用的是使用json來儲存資訊。json(JavaScript Object Notation)格式最初是為JavaScript 開發的,但隨後成了一種常見格式,並被包括Python在內的眾多語言採用。以下是一個經過了重構的儲存使用者資訊的例子:

import json

def get_stored_username(filename):
    """如果儲存了使用者名稱,就獲取它"""
    try:
        with open(filename) as f_obj:
            username = json.load(f_obj)
    except FileNotFoundError:
        return None
    else:
        return username

def get_new_username(filename):
    """提示使用者輸入使用者名稱,並存入檔案"""
    username = input("What's your name?")
    with open(filename, "w") as f_obj:
        json.dump(username, f_obj)
    return username

def greet_user(filename):
    """向使用者打招呼"""
    username = get_stored_username(filename)
    if username:
        print("Welcome back, " + username + "!")
    else:
        username = get_new_username(filename)
        print("We'll remember you when you come back, " + username + "!")

filename = "username.json"
greet_user(filename)
複製程式碼

程式碼就不執行了,請各位自行推導程式的結果。最後在username.json檔案中會存有使用者的資訊。但要注意一點,json根據資料型別來儲存資料,雖然最後都是字串,但這個過程不需要我們干預,比如要存一個列表,並不需要我們先將其轉換為字串,再存入json,讀取資料時也不需要我們先讀取為字串,再轉換成列表,我們只需直接存取即可,轉換工作由json模組自動完成。


迎大家關注我的微信公眾號"程式碼港" & 個人網站 www.vpointer.net ~

Python學習之路9-檔案和異常

相關文章