iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
1
自我挑戰組

30天學python系列 第 11

[Day11] 文件操作和異常處理

  • 分享至 

  • twitterImage
  •  

Python 中內置的 open 函數,我們可以指定文件名和操作模式,編碼信息等來獲得操作文件的對象。
操作模式指的是要打開什麼樣的文件(字符文件還是二進制文件)以及做什麼樣的操作,具體的如下表所示。
https://ithelp.ithome.com.tw/upload/images/20190925/20121116cnIAdE8LDv.png

讀寫文件

使用 open 函數時指定好帶路徑的文件名(可以使用相對路徑或絕對路徑)並將文件模式設置為'r'(如果不指定,默認值也是'r'),通過然後 encoding 參數指定編碼(如果不指定,默認值是無,那麼在讀取文件時使用的是操作系統默認的編碼),如果不能保證文件使用的編碼方式與編碼參數指定的編碼方式是否一致的,那麼就可能因無法解碼字符而導致讀取失敗。

def main():
    f = open('abc.txt', 'r', encoding = 'utf-8')
    print(f.read())
    f.close()

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116u4fSsnOEIv.png
https://ithelp.ithome.com.tw/upload/images/20190925/20121116wRTD6IcPc2.png
如果 open 函數指定的文件不存在或無法打開,那麼將引發異常狀況導致程序崩潰。
為了讓代碼有一定的容錯性,我們可以使用的 Python 的異常機制對可能在運行時發生狀況的代碼進行適當的處理。

def main():
    f = None
    try:
        f = open('a.txt', 'r', encoding = 'utf-8')
        print(f.read())
    except FileNotFoundError:
        print('無法打開指定的文件!')
    except LookupError:
        print('指定了未知的編碼!')
    except UnicodeDecodeError:
        print('讀取文件時解碼錯誤!')
    finally:
        if f:
            f.close()

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/201211163OjQ2aydgr.png
在 Python 中,可以將那些在運行時可能會出現狀況的代碼放在 try 代碼塊中,在 try 代碼塊的後面可以跟上一個或多個 except 來取得可能出現的異常狀況。
例如在上面讀取文件的過程中,文件找不到會引發 FileNotFoundError,指定了未知的編碼會引發 LookupError,而如果讀取文件時無法按指定方式解碼會引發 UnicodeDecodeError,在 try 後面跟上了三個 except 分別處理這三種不同的異常狀況。
最後我們使用 finally 代碼塊來關閉打開的文件,釋放掉程序中獲取的外部資源,由於 finally 的代碼不論程序正常還是異常都會執行到(甚至的英文調用了 sys 模組的 exit 函數退出 Python 環境,finally 都會被執行,因為exit 實質上函數的英文引發了 SystemExit 異常),因此通常我們把 finally 塊稱為'總是執行代碼塊',適合用來做釋放外部資源的操作。如果在不用 finally 關閉文件對象釋放資源,也可以使用上下文語法,通過 with 關鍵字指定文件對象的上下文環境並在離開上下文環境時釋放文件資源。

def main():
    try:
        with open('A.txt', 'r', encoding = 'utf-8') as f:
            print(f.read())
    except FileNotFoundError:
        print('無法打開指定的文件!')
    except LookupError:
        print('指定了未知的編碼!')
    except UnicodeDecodeError:
        print('讀取文件時解碼錯誤!')

if __name__ == '__main__':
    main()

除了使用 read 方法讀取文件之外,可以還使用 for-in 循環逐行讀取或用 readlines 方法將文件按行讀取到一個列表容器中。

import time

def main():
    # 一次性讀取整個文件內容
    with open('abc.txt', 'r', encoding = 'utf-8') as f: 
        print(f.read())

    print('- - - - - - - - - - - - - - - - - - - - - - - - - - ')
    # 使用 for-in 逐行讀取
    with open('abc.txt', mode = 'r') as f:  
        for line in f:
            print(line, end ='')
            time.sleep(0.5)
    print()
    # 讀取文件按行讀取到列表中
    with open('abc.txt') as f:      
        lines = f.readlines()
    print(lines)
    
if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116BYRCD5WG5N.png
使用 open 函數指定好文件名並將文件模式設置為 'w' 將文本訊息寫入文件。如果需要對文件內容進行追加式寫入,應該將模式設置為 'a'。如果要寫入的文件不存在會自動創建文件而不是引發異常。
下面的例子演示如何將 1 ~ 9999 之間的質數分別寫入三個文件中(1 ~ 99 之間的質數保存在 a.txt 中, 100 ~ 999 之間的質數保存在 b.txt 中,1000 ~ 9999之間的質數保存在 c.txt 中)。

from math import sqrt

def is_prime(n):        # 判斷質數
    assert n > 0
    for factor in range(2, int(sqrt(n)) + 1):
        if n % factor == 0:
            return False
    return True if n != 1 else False

def main():
    filenames = ('a.txt', 'b.txt', 'c.txt')
    fs_list = []
    try:
        for filename in filenames:
            fs_list.append(open(filename, 'w', encoding = 'utf-8'))
        for number in range(1, 10000):
            if is_prime(number):
                if number < 100:
                    fs_list[0].write(str(number) + '\n')
                elif number < 1000:
                    fs_list[1].write(str(number) + '\n')
                else:
                    fs_list[2].write(str(number) + '\n')
    except IOError as ex:
        print(ex)
        print('寫文件時發生錯誤!')
    finally:
        for fs in fs_list:
            fs.close()
    print('操作完成!')

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/20121116hgnH3UTOaF.png
https://ithelp.ithome.com.tw/upload/images/20190925/20121116m3FTKVAeTu.png

讀寫二進制文件

下面的程式碼展現了複製圖片文件的功能。

def main():
    try:
        with open('A.jpg', 'rb') as fs1:
            data = fs1.read()
            print(type(data))  
        with open('B.jpg', 'wb') as fs2:
            fs2.write(data)
    except FileNotFoundError as e:
        print('無法打開指定的文件!')
    except IOError as e:
        print('讀寫文件時發生錯誤!')
    print('程式執行结束.')

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20190925/201211164oAfVSMiZh.png
https://ithelp.ithome.com.tw/upload/images/20190925/20121116QrWOkjeYIs.png

讀寫 JSON 文件

如果希望把一個列表或者一個字典中的數據保存到文件中,利用 JSON 格式將數據進行保存。因為 JSON 也是純文本,任何系統任何編程語言處理純文本都是沒有問題的。目前 JSON 基本上已經取代了 XML 作為異構系統間交換數據的事實。
JSON 跟 Python 中的字典其實是一樣的,事實上 JSON 的數據類型和 Python 中的數據類型是很容易找到對應關係的,如下面兩張表所示。
https://ithelp.ithome.com.tw/upload/images/20190925/20121116ji4IaupKAb.pnghttps://ithelp.ithome.com.tw/upload/images/20190925/201211166Xb1ApVYT5.png
使用 Python 中的 json 模組就可以將字典或列表以 JSON 格式保存到文件中。

import json

def main():
    mydict = {
        'name': 'Andy',
        'age': 38,
        'bd': 700101,
        'friends': ['Alan', 'Amy'],
        'cars': [
            {'brand': 'BYD', 'max_speed': 180},
            {'brand': 'Audi', 'max_speed': 280},
            {'brand': 'Benz', 'max_speed': 320}
        ]
    }
    try:
        with open('data.json', 'w', encoding = 'utf-8') as fs:
            json.dump(mydict, fs)
    except IOError as e:
        print(e)
    print('保存數據完成!')

if __name__ == '__main__':
    main()

json 模組主要有四個比較重要的函數:

  1. dump - 將 Python 物件按照 JSON 格式序列化到文件中
  2. dumps - 將 Python 物件處理成 JSON 格式的字串
  3. load - 將文件中的 JSON 數據反序列化成 Python 物件
  4. loads - 將字串的內容反序列化成 Python 物件

序列化在計算機科學的數據處理中,是指將數據結構或對象狀態轉換為可以存儲或傳輸的形式,在需要的時候能夠恢復到原先的狀態,而且通過序列化的數據重新獲取字節時,可以利用這些字節來產生原始對象的副本。與這個過程相反的動作,從一系列字節中提取數據結構的操作,就是反序列化。
在 Python 的中要實現序列化和反序列化除了使用 json 模組之外,還可以使用 pickle 和 shelve 模組,但是這兩個模組是使用特有的序列化協議來序列化數據,因此序列化後的數據只能被 Python 識別。

最後一個範例因為要自行申請 APIkey,所以就不在這裡呈現了,想試試的人也可以自行測試。


上一篇
[Day10] 圖形使用者介面 (GUI) 和遊戲開發
下一篇
[Day12] 字串和正規表示式
系列文
30天學python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言